Skip to main content

Solver-based attribute resolver for OpenVCAD.

Project description

pyvcad_attribute_resolver

Solver-based attribute resolution for OpenVCAD models. Automatically converts design-intent attributes (e.g. density, shore hardness) into machine-level attributes (e.g. nozzle temperature, flow-rate multiplier) using registered conversion data.

See the main OpenVCAD package for more info. For a deep-dive into the architecture, see design_doc.md.

Quick Start

import pyvcad as pv
import pyvcad_attribute_resolver as resolver

# Load built-in foaming filament data
resolver.register_foaming_conversions()

# Build a design with a physical attribute
sphere = pv.Sphere(pv.Vec3(0, 0, 0), 10.0)
sphere.set_attribute(pv.DefaultAttributes.TEMPERATURE,
                     pv.FloatAttribute("x + 210"))

# The resolver figures out: temperature -> flow_rate
result = resolver.adapt(sphere, ["flow_rate"], tags=["foaming_tpu"])

API Reference

Function Description
register_conversion(source, target, converter_factory, ...) Register a new conversion edge
unregister_conversion(name) Remove a conversion by name
list_conversions() List all registered ConversionEntry objects
clear_conversions() Remove all registered conversions
adapt(design, targets, tags=..., ...) Wrap a design node with automatic conversions
list_modules() Discover installed domain modules
register_foaming_conversions() Register all 5 foaming filament conversions
register_tpu_conversions() Register VarioShore TPU conversions only
register_pla_conversions() Register LW-PLA conversions only
register_j750_shore_hardness_conversions() Register J750 shore-hardness to volume-fractions conversions
register_j750_modulus_toughness_conversions() Register the J750 modulus+toughness inverse-design conversion
register_generic_color_conversions(alpha=1.0) Register machine-agnostic COLOR_RGB <-> COLOR_RGBA conversions
generate_j750_modulus_toughness_lookup_table(...) Regenerate the bundled J750 inverse lookup table
generate_j750_modulus_toughness_reachable_region_plot(...) Regenerate the J750 reachable-region validation plot

Adding a New Conversion Module

This section walks through adding your own material data to the resolver.

1. Create the Module Folder

Create a new sub-package under modules/:

resolver/src/pyvcad_attribute_resolver/modules/
└── your_material/
    ├── __init__.py
    └── data.py          # or .csv, .json — whatever suits your data

2. Add Your Data

Put your empirical measurements in data.py. Each dataset is a list of (input_value, output_value) tuples:

# data.py

#: Density (g/cm³) -> temperature (°C)
DENSITY_TO_TEMPERATURE = [
    (0.50,  260),
    (0.65,  250),
    (0.80,  240),
    (1.00,  230),
]

#: Temperature (°C) -> flow rate multiplier
TEMPERATURE_TO_FLOW_RATE = [
    (230, 1.20),
    (240, 1.00),
    (250, 0.85),
    (260, 0.75),
]

Tip: If your data lives in CSV files, you can load them in data.py using importlib.resources or pathlib.Path(__file__).parent to locate the files relative to the module.

3. Write Registration Functions

In __init__.py, create factory functions and register them:

# __init__.py
import pyvcad as pv
from pyvcad_attribute_resolver.registry import register_conversion
from .data import DENSITY_TO_TEMPERATURE, TEMPERATURE_TO_FLOW_RATE


def _lut_factory(source_attr, target_attr, data):
    """Return a callable that builds a fresh LookupTableConverter."""
    entries = [pv.LookupTableEntry(kv[0], kv[0], kv[1]) for kv in data]

    def _factory():
        return pv.LookupTableConverter(
            [source_attr], [target_attr],
            entries, pv.InterpolationMode.LINEAR,
        )
    return _factory


def register_your_material_conversions():
    """Register all conversions for your material."""
    register_conversion(
        source="density",
        target="temperature",
        converter_factory=_lut_factory(
            pv.DefaultAttributes.DENSITY,
            pv.DefaultAttributes.TEMPERATURE,
            DENSITY_TO_TEMPERATURE,
        ),
        name="your_material_density_to_temperature",
        priority=0,
        tags=["your_material"],
    )
    register_conversion(
        source="temperature",
        target="flow_rate",
        converter_factory=_lut_factory(
            pv.DefaultAttributes.TEMPERATURE,
            pv.DefaultAttributes.FLOW_RATE,
            TEMPERATURE_TO_FLOW_RATE,
        ),
        name="your_material_temperature_to_flow_rate",
        priority=0,
        tags=["your_material"],
    )

Key points:

  • name must be globally unique across all modules.
  • tags should identify your material so users can filter with tags=["your_material"]. Use distinct tags to avoid ambiguity with other modules that define the same conversion edges.
  • required_inputs should list every attribute the converter actually consumes. Leave it unset for ordinary one-input conversions; set it explicitly for cases like modulus + toughness -> volume_fractions.
  • converter_factory must be a zero-arg callable that returns a new converter instance each time.

J750 Modulus/Toughness Module

The built-in j750_modulus_toughness module inverse-designs volume_fractions from two design-intent attributes:

  • modulus in MPa
  • toughness in MJ/m^3

It uses the legacy C++ model to generate a 128 x 128 lookup table over:

  • log10(modulus [MPa]) in [-1.0, 3.5]
  • toughness [MJ/m^3] in [0.0, 6.5]

The shipped runtime table assumes the J750 materials are:

  • Agilus30Clr
  • VeroBlack
  • M.Cleanser

and uses a default maximum cleanser volume fraction of 0.30.

To register the built-in table:

import pyvcad_attribute_resolver as resolver

resolver.register_j750_modulus_toughness_conversions()

To regenerate the lookup table with a different liquid cap or resolution:

resolver.generate_j750_modulus_toughness_lookup_table(
    output_path="j750_custom_lut.json",
    table_size=192,
    max_liquid_volume=0.25,
    fail_threshold=0.05,
)

Then register that table explicitly:

resolver.register_j750_modulus_toughness_conversions(
    lookup_table_path="j750_custom_lut.json",
)

The generator stores a separate validity mask alongside the fraction grid. Invalid cells represent unreachable modulus/toughness targets; runtime lookup raises immediately instead of clamping to a nearby printable mixture.

4. Wire It Into the Package

Add a convenience re-export in the top-level __init__.py:

# resolver/src/pyvcad_attribute_resolver/__init__.py
from .modules.your_material import register_your_material_conversions

5. Use It

import pyvcad as pv
import pyvcad_attribute_resolver as resolver

resolver.register_your_material_conversions()

design = pv.Sphere(pv.Vec3(0, 0, 0), 10.0)
design.set_attribute(pv.DefaultAttributes.DENSITY,
                     pv.FloatAttribute(0.7))

# Automatically resolves: density -> temperature -> flow_rate
result = resolver.adapt(design, ["temperature", "flow_rate"],
                        tags=["your_material"])

6. Add Tests

Create a test file in resolver/tests/ (see test_foaming.py for an example):

# resolver/tests/test_your_material.py
import pytest
import pyvcad as pv
import pyvcad_attribute_resolver as resolver

@pytest.fixture(autouse=True)
def clean():
    resolver.clear_conversions()
    yield
    resolver.clear_conversions()

def test_registration():
    resolver.register_your_material_conversions()
    names = {e.name for e in resolver.list_conversions()}
    assert "your_material_density_to_temperature" in names
    assert "your_material_temperature_to_flow_rate" in names

def test_adapt_chain():
    resolver.register_your_material_conversions()
    sphere = pv.Sphere(pv.Vec3(0, 0, 0), 1.0)
    sphere.set_attribute("density", pv.FloatAttribute(0.7))
    result = resolver.adapt(sphere,
                            ["temperature", "flow_rate"],
                            tags=["your_material"])
    assert "temperature" in result.attribute_list()
    assert "flow_rate" in result.attribute_list()

Run with:

cd resolver && python -m pytest tests/ -v

Module Directory Structure

resolver/src/pyvcad_attribute_resolver/
├── __init__.py              # core public API
├── adapt.py                 # adapt() entry point
├── conversion_entry.py      # ConversionEntry dataclass
├── conversion_graph.py      # directed graph
├── registry.py              # global register/unregister/list/clear
├── solver.py                # BFS pathfinder
└── modules/
    ├── __init__.py           # list_modules() discovery
    ├── foaming/              # built-in foaming filament data
    │   ├── __init__.py
    │   └── data.py
    └── your_model(s)/        # your new module
        ├── __init__.py
        └── data.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyvcad_attribute_resolver-3.0.0-py3-none-any.whl (810.4 kB view details)

Uploaded Python 3

File details

Details for the file pyvcad_attribute_resolver-3.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pyvcad_attribute_resolver-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e8a5425619121d85c432b54ad82a458a9130c610387b1babbd5b98ff750fe8f
MD5 94d5387a945c1a10f30d028c358b2e6c
BLAKE2b-256 c35194b4c07170a9c33b2237a501823ceb442a310d047cd5540fe6f6ba036c97

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page