The Python API runs the same pipeline as the CLI, but keeps each conversion step explicit and composable. The package is PEP 561 typed (py.typed ships in the wheel), so mypy and IDEs see the full strict-mode annotations. import fascat is lazy — heavy stacks (numpy, Pillow, runtime harnesses) load on first use, so importing the package costs almost nothing.

Start here

The simplest path is one call. It runs the full default pipeline for a profile and validates the output:

import fascat as fc

asset = fc.convert("motor.step", "motor.glb", profile="realtime-web")
print(asset.report.summary())

When you need control over individual steps, build the pipeline yourself. Each method returns a new Asset and accepts keyword arguments directly — set only what you want to change:

import fascat as fc

asset = (
    fc.read_step("motor.step")          # or read_iges(...) / read_brep(...) / read_jt(...)
    .tessellate(sag=0.1, angle=15.0)
    .repair(tolerance=0.05)
    .stage(materials="cad", uv0="box")
    .optimize(target_triangles=500_000)
    .lods([0.5, 0.25, 0.1])
)

asset.write_gltf("motor.glb")
asset.write_usd("motor.usdc")

Keyword arguments mirror the matching *Options dataclass field-for-field. For a prebuilt configuration, pass options= (or the options object positionally) instead — never both: asset.repair(fc.RepairOptions(tolerance=0.05)). Write methods accept dry_run=True to validate the destination and record the write report step without creating or replacing the output file.

Use asset.is_empty, asset.has_meshes, and asset.has_lods for quick guards before expensive operations. For Python mesh tooling, mesh.to_trimesh() returns a copied trimesh.Trimesh; asset.to_trimesh(include_lods=False) returns a trimesh.Scene with one geometry per mesh-bearing occurrence and node transforms preserved. Pass include_lods=True to include generated LOD meshes as sibling scene nodes.

The rest of this page documents each step and every option. Two things apply throughout:

  • Reports. Write calls append a write step to the asset report, and every step

records its options, before/after counts, and warnings. See [Reports and stats](#reports-and-stats).

  • Parallelism. Mesh-heavy per-part operations fan independent parts out to a

process pool and reassemble them in deterministic order. jobs defaults to min(4, CPU count) on RepairOptions, MergeVerticesOptions, StageOptions, OptimizeOptions, DecimateOptions, LODOptions, and LODGeneratorOptions; the FASCAT_JOBS environment variable overrides the default, and jobs=1 disables pooling entirely. Worker processes start via spawn, so assemblies with only a handful of small parts can be faster with jobs=1.

Mutability and ownership

Asset, Part, and Mesh are mutable Python objects, but Fascat owns the containers you pass to their constructors. Creating an Asset copies the root tree, parts, materials, images, metadata, PMI list, report, and every nested mesh array. Creating a Mesh copies points, faces, normals, tangents, UVs, material indices, and face groups. Later changes to the original input arrays or dicts do not alter the constructed object.

Processing methods such as tessellate(), repair(), stage(), optimize(), and lods() follow copy-on-operation semantics: they return a new Asset and leave the receiver unchanged. Use asset.copy() when you want a manual fork of the current scene. asset.copy(keep_source=False) drops backend source handles but still copies the public scene graph and mesh arrays.

Internal hot paths use private adoption helpers only after they have already built owned roots, parts, materials, images, metadata, PMI annotations, and reports. That avoids a second full deep copy of large assemblies while keeping the public constructors safe. _adopt is private and assumes the caller owns every object it passes.

Direct mutation is available for low-level workflows:

part = asset.parts["housing"]
assert part.mesh is not None
part.mesh.points[:, 2] += 2.0
part.mesh.validate()
part.fingerprint = part.mesh.fingerprint()

When you mutate asset.root, asset.parts, or mesh arrays directly, you are responsible for keeping node part ids, part dictionaries, mesh shapes, face indices, material slots, and Part.fingerprint consistent. Prefer the pipeline methods when you want report steps, warnings, scoped operation handling, and automatic fingerprint updates.

Core pipeline calls:

APIParametersPurpose
fc.read_step(path, options=None)path is a STEP file path or - for stdin. options is StepReadOptions.Import STEP assembly hierarchy, metadata, materials, and source BREP handles when the backend exposes them. With StepReadOptions(multi_file=True), quoted external .step / .stp references are recursively resolved from a master file and imported as deterministic member occurrences.
fc.read_step_many(paths, options=None, continue_on_error=False)paths is an ordered list of .step / .stp files.Import explicit multi-root STEP members into deterministic per-file namespaces, prefix member warnings, and preserve each member as a top-level root.
fc.read_iges(path, options=None)path ends in .igs or .iges. options is IgesReadOptions.Import IGES geometry through the same OCP/XDE hierarchy, transform, color, and material path used by STEP.
fc.read_brep(path, options=None)path ends in .brep. options is BrepReadOptions.Import a native OpenCASCADE BREP file as one root occurrence and one source-shape part.
fc.read_jt(path, options=None)path ends in .jt. options is JtReadOptions (adds `lod_selection="finest"\"all"`).Import JT 8.x/9.x/10.x pre-tessellated LOD meshes, assembly hierarchy, instances, materials, and properties with a pure-Python reader; parts are mesh-only (source_shape is None).
asset.tessellate(options=None, *, where=None, **kwargs)Keyword args mirror TessellationOptions. where optionally scopes the operation with a Filter.Convert source BREP geometry into meshes.
asset.repair(options=None, *, where=None, **kwargs)Keyword args mirror RepairOptions. where optionally scopes selected parts.Clean mesh-level issues after tessellation.
asset.merge_vertices(options=None, *, where=None, **kwargs)Keyword args mirror MergeVerticesOptions. where optionally scopes selected parts.Merge exact or tolerance-close vertices with attribute and material-boundary protection.
asset.delete_degenerate_polygons(options=None, *, where=None, **kwargs)Keyword args mirror DeleteDegeneratePolygonsOptions. where optionally scopes selected parts.Remove repeated-vertex, duplicate, or near-zero-area triangles as a standalone cleanup step.
asset.stage(options=None, *, where=None, **kwargs)Keyword args mirror StageOptions. where optionally scopes selected parts.Prepare materials, normals, tangents, and UV metadata for runtime export.
asset.optimize(options=None, *, where=None, **kwargs)Keyword args mirror OptimizeOptions. where optionally scopes selected parts.Reduce mesh complexity while preserving selected mechanical features.
asset.lods(options=None, *, where=None, **kwargs)options may also be a bare ratio sequence or LODGeneratorOptions; keyword args mirror LODOptions. where optionally scopes selected parts.Generate lower-detail runtime meshes.
asset.write_usd(path, options=None, dry_run=False)path ends in .usd, .usda, .usdc, or .usdz. options is UsdExportOptions.Write OpenUSD output and append a write step to the report.
asset.write_gltf(path, options=None, dry_run=False)path ends in .gltf or .glb. options is GltfExportOptions.Write glTF 2.0 output and append a write step to the report.
asset.write_obj(path, options=None, dry_run=False)path ends in .obj. options is ObjExportOptions.Write Wavefront OBJ output and append a write step to the report.
asset.write_stl(path, options=None, dry_run=False)path ends in .stl. options is StlExportOptions.Write STL output and append a write step to the report.
asset.write_fbx(path, options=None, dry_run=False)path ends in .fbx. options is FbxExportOptions.Write ASCII FBX output and append a write step to the report.

Asset tree model

An Asset stores assembly structure separately from geometry. asset.root is a Node tree. A Node with part_id is an occurrence: it places one reusable Part in the hierarchy with that node's transform and metadata. asset.parts[part_id] owns the source shape, mesh, material slots, per-part metadata, and generated LOD meshes. Multiple occurrence nodes may reference the same part, which is how instancing survives import and optimization.

for node in asset.root.walk():
    if node.part_id is None:
        continue
    part = asset.parts[node.part_id]
    print(node.name, part.name, part.mesh.triangle_count if part.mesh else 0)

Most asset operations return a new Asset and leave the input asset unchanged. When where= selects only one occurrence of a shared part, Fascat isolates the selected occurrence before mutating it so unselected occurrences keep referencing the original part.

Catching Errors

fc.FascatError is the canonical base class for Fascat-owned failures. fc.Error is the same class, kept as a short alias for concise catches. Public readers and writers wrap RuntimeError, ValueError, and OSError as fc.FascatIOError, preserving the original exception on exc.__cause__. Validation helpers and non-I/O option constructors may still raise standard exceptions directly.

import fascat as fc

try:
    asset = fc.convert("motor.step", "motor.glb", profile="realtime-web")
except fc.FascatError as exc:
    report = getattr(exc, "report", None)
    if report is not None:
        report.write_json("failed-convert-report.json")
    raise

Use read_step_many(..., continue_on_error=True) when importing several independent STEP roots and you want successful members preserved while failed members are recorded as report warnings.

Assembly filters

Use Filter selectors to inspect or process one branch of an assembly while leaving the rest unchanged. Scalar counts such as triangle_count, vertex_count, and draw_call_count are properties. Aggregate summaries such as stats() and draw_call_breakdown() stay as methods because they allocate dictionaries and can include optional detail. select() and its selection() alias are inspection-only; use the where= keyword to scope mutating operations. Use clone() for public asset copies. copy(keep_source=...) remains available for advanced source-handle control.

import fascat as fc

asset = fc.read_step("motor.step").tessellate()

fasteners = fc.Filter(
    path="*/Fasteners/*",
    name=["Bolt*", "Nut*", "Washer*"],
)

large_castings = fc.Filter.all(
    fc.Filter.path("*/Housing/*"),
    fc.Filter.size(min_diagonal=50.0),
)

print(asset.select(fasteners).stats())

asset = asset.optimize(
    fc.OptimizeOptions(target_triangles=80_000),
    where=fasteners,
)

asset = asset.stage(
    fc.StageOptions(materials="display", uv0="none", uv1=None),
    where=large_castings,
)

Filters support node path, node name, part id, part name, material, metadata, bounding box, size, triangle count, vertex count, and logical all, any, and not_ composition. Most where parameters accept a Filter, an explicit expression string such as part=bolt or path=*/Fasteners/*, or a criteria dict such as {"part": "bolt"}. Bare strings are not guessed; use a key/value expression so the matching field is explicit. Pattern fields accept either one string or a sequence of strings; multiple patterns are OR-matched. String patterns use Python fnmatchcase semantics, so matching is case-sensitive shell-style glob matching, not regular expressions. If a selected occurrence shares a part with an unmatched occurrence, Fascat duplicates the selected occurrence's part before applying the operation so the unmatched branch stays intact. The scope planner skips that isolation copy when the selection already maps cleanly to whole unique parts. Report steps include where and matched fields when an operation is scoped.

Filter parameters:

ParameterMeaning
pathMatch the full assembly node path with shell-style patterns such as */Fasteners/*.
nameMatch node names. Accepts a string or list of patterns.
part_nameMatch the source part name.
part_idMatch the stable Fascat part id. Filter.part(value) is shorthand for this.
materialMatch any material assigned to the selected part.
metadataRequire metadata key/value matches on the node, part, material, or asset context.
min_bounds, max_boundsMatch parts whose bounding box lies inside the supplied coordinate bounds.
min_diagonal, max_diagonalMatch by bounding-box diagonal size.
min_triangles, max_trianglesMatch by mesh triangle count. Filter.triangle_count() builds these criteria.
min_vertices, max_verticesMatch by mesh vertex count. Filter.vertex_count() builds these criteria.
includeRequire at least one nested filter to match before criteria are accepted.
excludeDrop matches selected by nested filters.
Filter.all(...)Require every child filter to match.
Filter.any(...)Require at least one child filter to match.
Filter.not_(...)Invert one child filter.
whereMost pipeline methods accept where=Filter(...), where="part=...", or where={"part": ...} to scope an operation without destroying unmatched hierarchy.

Hierarchy merge

Use merge() to reduce node count and draw calls before optimization.

import fascat as fc

asset = fc.read_step("motor.step").tessellate().stage()

asset = asset.merge(
    fc.MergeOptions(
        mode="by_material",
        keep_parent=True,
        metadata="combine",
        max_vertices_per_mesh=65_535,
        preserve_materials=True,
    ),
    where=fc.Filter.path("*/Fasteners/*"),
)

Merge modes are all, by_material, by_node_name, by_part_name, hierarchy_level, parent_children, final_level, and regions. Merging bakes node transforms into merged vertex positions, keeps material slots when requested, and removes replaced empty nodes. The merge report step records a before/after draw-call breakdown (draw_calls, draw_call_meshes, draw_call_materials, draw_call_submesh_slots, draw_call_material_slots, draw_call_mesh_instances, draw_call_reused_instances, draw_call_instanced_meshes, draw_call_merged_batches). When merging reduces reusable instances, the step adds an export_advisor entry and warning so the GLB file-size, memory, and culling tradeoff is explicit.

Use explode() when runtime tools need separate meshes by material or connected component, and replace() when a selected part should become a proxy.

asset = asset.explode(
    fc.options.ExplodeOptions(mode="connected_components"),
    where=fc.Filter.material("rubber"),
)

asset = asset.replace(
    fc.options.ReplaceOptions(mode="bounding_box", preserve_transform=True),
    where=fc.Filter.triangle_count(max=12),
)

ReplaceOptions(mode="external_asset", external_path="proxy.glb") records an external proxy reference while keeping a bounding-box mesh fallback in the asset.

Hierarchy option parameters:

OptionParameterMeaning
MergeOptionsmodeMerge strategy: all, by_material, by_node_name, by_part_name, hierarchy_level, parent_children, final_level, or regions.
MergeOptionskeep_parentKeep a selected parent node and place merged geometry under it instead of flattening the selected branch completely.
MergeOptionsmetadataMetadata policy: preserve, combine, summarize, or drop.
MergeOptionsmax_vertices_per_meshSplit merged output before it exceeds this vertex count. Use 65_535 for 16-bit index friendly meshes.
MergeOptionspreserve_materialsKeep material slots and face material assignments in merged geometry.
MergeOptionshierarchy_levelLevel used by mode="hierarchy_level". 0 starts at the selected root.
MergeOptionsregion_sizeSpatial cell size used by mode="regions". Required for region merging.
MergeOptionsmerge_strategySub-strategy inside region merging: all or by_material.
MergeOptionsremove_empty_nodesRemove hierarchy nodes left empty after merging.
ExplodeOptionsmodeSplit selected meshes by by_material or connected_components.
ExplodeOptionsmetadataMetadata policy applied to exploded parts.
ExplodeOptionsremove_empty_nodesRemove empty source nodes after selected geometry is replaced by exploded children.
ReplaceOptionsmodeReplacement style: bounding_box, proxy_mesh, or external_asset.
ReplaceOptionspreserve_transformKeep the selected occurrence transform on the replacement.
ReplaceOptionsmetadataMetadata policy applied to replacement parts.
ReplaceOptionsproxy_meshMesh object required when mode="proxy_mesh".
ReplaceOptionsexternal_pathExternal asset path recorded when mode="external_asset".

Metadata and PMI

Fascat keeps top-level asset metadata and typed PMI records alongside node, part, material, and mesh metadata.

import fascat as fc

asset = fc.read_step(
    "motor.step",
    options=fc.options.StepReadOptions(
        metadata=True,
        product_metadata=True,
        properties=True,
        layers=True,
        validation_properties=True,
        pmi=True,
        design_variants=False,
        design_variant_selection=(),
        existing_meshes=True,
        multi_file=False,
        source_textures=True,
        source_texture_search_paths=("textures",),
        material_library_mapping=True,
        material_library_paths=("vendor-materials.json",),
        delete_free_vertices=False,
        delete_lines=False,
        source_units=None,
        source_up_axis="Z",
        source_handedness="right",
        target_units="metre",
        target_up_axis="Y",
        target_handedness="right",
    ),
)

asset.metadata["review_state"] = "approved"
asset.pmi.append(
    fc.PmiAnnotation(
        id="pmi_001",
        kind="dimension",
        text="25.4 +/-0.1",
        value=25.4,
        unit="millimetre",
        tolerance=fc.Tolerance(upper=0.1, lower=0.0),
        applies_to=["part_123"],
    )
)

glTF export writes metadata and PMI into extras.fascat. USD export writes Fascat metadata into customData on the scene, nodes, prototypes, materials, meshes, and /PMI/* annotation prims. When merge, explode, or replace operations create new parts, exporters resolve PMI links through source_part_id and source_part_ids metadata so annotations that targeted the original part still attach to the derived output.

PMI import

When pmi=True, STEP AP242 import runs a textual scan and turns supported records into typed PmiAnnotation objects with source STEP entity ids, references, and numeric tolerance bounds where available. ISO-10303-21 string escape directives (\X2\…\X0\, \X4\…\X0\, \X\HH, \S\, \P?\, \\) are decoded in PMI text, design-variant labels, and external/texture references; malformed sequences stay literal. Supported record families:

  • DimensionsDIMENSIONAL_SIZE, DIMENSIONAL_LOCATION
  • TolerancesPLUS_MINUS_TOLERANCE, GEOMETRIC_TOLERANCE, and named subtypes such as FLATNESS_TOLERANCE, POSITION_TOLERANCE, SURFACE_PROFILE_TOLERANCE
  • DatumsDATUM, DATUM_REFERENCE, DATUM_TARGET
  • CalloutsFEATURE_CONTROL_FRAME and annotation text entities

Import reports and asset metadata also include pmi_semantic_graph: a textual STEP reference graph of PMI entity nodes, referenced entities, shape-aspect/product targets, inbound callout/associativity records, tolerance-zone and annotation-presentation support records, reference edges, and missing-reference counts.

If a file advertises AP242 PMI but no supported record is extracted, the import report records pmi_present=true, unsupported_pmi_count=1, and a warning rather than implying PMI was imported. metadata_and_visuals export adds deterministic glTF/USD marker meshes with simple vector text glyphs linked to these records.

Full AP242 graphical presentation reconstruction and semantic coverage beyond

these textual records is planned backend work.

Design variants

When design_variants=True, import scans STEP configuration, effectivity, and condition records into asset metadata and the import report, with counts, STEP references, resolved reference labels, effectivity values, condition operators, and parsed literal values. Pass design_variant_selection=(...) to prune geometry by selected variant.

A selection value can be a variant label, an effectivity value or range, a STEP record id, a referenced label, or a label=value assignment — for example ("left hand",), ("SN-A-050",), ("load rating=15",), ("finish=black anodized",), or ("service enabled=false",). Conditions are evaluated before their operand labels can drive pruning: an AND_EXPRESSION selects only when all operands match, EQUALS_EXPRESSION / COMPARISON_EQUAL only when operands resolve equal, APPLIED_INEFFECTIVITY_ASSIGNMENT suppresses its targets, and wrappers gate their configured targets. Operand-only expression labels are never promoted to geometry selectors.

Supported record families:

  • Configuration & effectivityCONFIGURATION_ITEM, PRODUCT_CONCEPT_FEATURE, CONFIGURATION_DESIGN, CONFIGURATION_EFFECTIVITY, PRODUCT_DEFINITION_EFFECTIVITY, SERIAL_NUMBERED_EFFECTIVITY, LOT_EFFECTIVITY, DATED_EFFECTIVITY, EFFECTIVITY_RELATIONSHIP
  • Boolean / comparison conditionsAND_EXPRESSION, OR_EXPRESSION, XOR_EXPRESSION, NOT_EXPRESSION, EQUALS_EXPRESSION, COMPARISON_EQUAL, COMPARISON_NOT_EQUAL, COMPARISON_GREATER, COMPARISON_GREATER_EQUAL, COMPARISON_LESS, COMPARISON_LESS_EQUAL, INTERVAL_EXPRESSION, LIKE_EXPRESSION
  • Numeric arithmeticPLUS_EXPRESSION, MINUS_EXPRESSION, MULT_EXPRESSION, DIV_EXPRESSION, SLASH_EXPRESSION, MOD_EXPRESSION, POWER_EXPRESSION, RATIONAL_REPRESENTATION_ITEM, EXPRESSION_EXTENSION_NUMERIC
  • Numeric functionsABS_FUNCTION, MINUS_FUNCTION, SQUARE_ROOT_FUNCTION, MAXIMUM_FUNCTION, MINIMUM_FUNCTION, SIN_FUNCTION, COS_FUNCTION, TAN_FUNCTION, ASIN_FUNCTION, ACOS_FUNCTION, ATAN_FUNCTION (unary, or binary as atan2), EXP_FUNCTION, LOG_FUNCTION, LOG2_FUNCTION, LOG10_FUNCTION, ODD_FUNCTION
  • String expressionsCONCAT_EXPRESSION, SUBSTRING_EXPRESSION, INDEX_EXPRESSION, FORMAT_FUNCTION, EXPRESSION_EXTENSION_STRING, and the string-to-numeric LENGTH_FUNCTION, VALUE_FUNCTION, INT_VALUE_FUNCTION
  • Literals & variablesBOOLEAN_LITERAL, BOOLEAN_REPRESENTATION_ITEM, LOGICAL_LITERAL, LOGICAL_REPRESENTATION_ITEM, BOOLEAN_VARIABLE, MATHS_BOOLEAN_VARIABLE, STRING_LITERAL, MATHS_STRING_VARIABLE / STRING_VARIABLE, numeric/string literals, and named maths variables
  • WrappersCONDITIONAL_CONFIGURATION, CONDITIONAL_CONCEPT_FEATURE, CONDITIONAL_EFFECTIVITY, CONFIGURED_EFFECTIVITY_ASSIGNMENT, APPLIED_EFFECTIVITY_ASSIGNMENT / APPLIED_INEFFECTIVITY_ASSIGNMENT, APPLIED_EFFECTIVITY_CONTEXT_ASSIGNMENT, CONFIGURED_EFFECTIVITY_CONTEXT_ASSIGNMENT, CLASS_USAGE_EFFECTIVITY_CONTEXT_ASSIGNMENT
  • Date bounds (for serial/date/interval ranges) — TIME_INTERVAL_WITH_BOUNDS, CALENDAR_DATE, ORDINAL_DATE, WEEK_OF_YEAR_AND_DAY_DATE

How values resolve:

  • Numeric comparisons and intervals match named numeric variables supplied as label=value, including values flowing through the arithmetic, function, and rational/extension records above (ATAN_FUNCTION evaluated as atan2; ODD_FUNCTION tests odd integers).
  • String equality, not-equality, and LIKE_EXPRESSION match named string variables, with */% and ?/_ wildcards, flowing through concat/substring/index/extension operands; FORMAT_FUNCTION can feed string matching from numeric variables, and LENGTH/VALUE/INT_VALUE functions feed numeric comparisons from strings (strict text parsing).
  • Boolean literals parse STEP .T. / .F.; boolean variables act as named operands selected by label, record id, or explicit label=true / label=false.

This matches configuration labels against loaded product names. Full AP242

conditional/effectivity geometry evaluation remains planned backend work.

Textures and materials

With source_textures=True, STEP/IGES import scans source-file string references for sidecar PNG, JPEG, and KTX2 textures, loads them as first-class ImageResource objects, and binds semantic names (baseColor, normal, ao, emissive) to material metadata. XDE visual PBR values are preserved where exposed, and common CAD material names (steel, aluminum, brass, copper, glass, plastic, rubber, paint) map to deterministic PBR defaults.

Vendor material libraries can be supplied via material_library_paths or referenced from the CAD source — as JSON/MTL files, ZIP packages, or folders. Imported records update matching CAD materials with PBR factors and texture slots, and the import report records resolved, missing, unreadable, matched, and unmatched counts.

Texture and material-library references found inside CAD file content are confined to the CAD source directory plus the configured search paths: absolute paths, .. traversal, and symlinks that escape every search root are reported as missing instead of being read. (External STEP assembly references are guarded separately by strict .step/.stp extension validation.) Library files passed explicitly via material_library_paths are trusted CLI/API input and are not subject to confinement.

Auxiliary text scans are also size-capped: STEP files over 64 MiB skip the textual PMI/variant/reference passes with a report warning (geometry import is unaffected), and sidecar material libraries (16 MiB) or textures (64 MiB) over their caps are reported unreadable instead of being loaded into memory.

Construction curves

Construction-only line shapes follow the construction_curve_policy:

  • preserve_metadata (default) — keep the source shape; report it has no triangle mesh
  • delete — drop construction-only line nodes during import (legacy delete_lines=True is an alias)
  • tessellate_tubes — convert curve segments into low-sided triangle tubes during tessellation, using construction_curve_tube_radius (source units)

STEP import also splits free construction edges out of mixed face+curve shapes so the same policy applies. With keep_brep=True, the original mixed topology stays available on the BREP part. Import reports record import_decisions (each toggle as requested/effective plus a status) and loaded_representations (per-part input kind and source topology counts).

Multi-file assemblies

Two cases, both imported through the normal STEP path with deterministic member namespacing:

  • Several root filesfc.read_step_many([...]), fc.convert([...], "out.glb"), or fascat convert root-a.step out.glb --input root-b.step. Members are namespaced and kept under a shared root; warnings are prefixed with member index and path. continue_on_error=True keeps successful members and records failures.
  • One master file with external referencesStepReadOptions(multi_file=True) (or --multi-file-import). Quoted .step/.stp references are resolved relative to the referencing file and followed once per source; repeated references become separate member occurrences. The report includes external_reference_graph (resolved/missing/unsupported/unique-source/occurrence counts), and missing references warn rather than disappear.

Multi-file import is graph-level loading, not deep reconstruction of every

vendor-specific external-reference placement transform.

Metadata and PMI parameters:

OptionParameterMeaning
StepReadOptionsmetadataEnables general source metadata import. If False, the more specific metadata import groups are disabled by default.
StepReadOptionsproduct_metadataImport product and assembly-level metadata where the STEP backend exposes it.
StepReadOptionspropertiesImport user and product properties.
StepReadOptionslayersRequest layer assignments as metadata. Current normalized layer extraction is reported as unsupported in import_decisions when requested.
StepReadOptionsvalidation_propertiesRequest STEP validation properties. Current reports approximate this with source topology counts rather than typed validation-property entities.
StepReadOptionspmiImport typed AP242 PMI records into PmiAnnotation objects and report the pmi_semantic_graph. See PMI import.
StepReadOptionsdesign_variantsScan STEP configuration, effectivity, and condition records into metadata and import reports. See Design variants.
StepReadOptionsdesign_variant_selectionOne or more selection values (variant labels, effectivity values/ranges, record ids, or label=value assignments) used to prune imported geometry. See Design variants for the full resolution rules.
StepReadOptionsexisting_meshesPrefer existing tessellation payloads from the source file when the importer exposes them. Tessellation reuse_existing_meshes still controls whether loaded meshes are retessellated later.
StepReadOptionsmulti_fileRequest multi-file STEP assembly import. read_step_many() honors explicit member lists; single-path STEP imports recursively resolve quoted external .step / .stp references, preserve repeated references as member occurrences, and report the external_reference_graph.
StepReadOptionssource_texturesScan STEP/IGES source text for referenced sidecar PNG/JPEG/KTX2 texture files, load resolved files into asset.images, and report resolved/missing/unreadable counts.
StepReadOptionssource_texture_search_pathsExtra directories used to resolve relative source texture references in addition to the CAD file directory.
StepReadOptionsmaterial_library_mappingApply deterministic CAD material-name mapping rules to PBR metallic, roughness, opacity, and default color values when source visual material names are available.
StepReadOptionsmaterial_library_pathsExplicit vendor material-library JSON/MTL/ZIP files or folders to load during STEP/IGES import. Referenced library files are resolved relative to the CAD source and texture search paths.
StepReadOptionsmaterial_library_color_spaceNumeric material-library color interpretation: auto preserves 0-1 or 0-255 detection, linear clamps direct factors, and srgb255 treats numeric colors as 0-255 values.
StepReadOptionsdelete_free_verticesDrop construction-only point shapes during import and record deletion counts in the import report.
StepReadOptionsdelete_linesLegacy alias for deleting construction-only line shapes during import. Free construction edges split from mixed face+curve shapes follow the same delete policy.
StepReadOptionsconstruction_curve_policyConstruction line policy for construction-only shapes and free construction edges split from mixed face+curve shapes: preserve_metadata, delete, or tessellate_tubes. Tube tessellation happens when the asset is tessellated.
StepReadOptionsconstruction_curve_tube_radiusTube radius in source units for construction_curve_policy="tessellate_tubes".
StepReadOptionssource_units, source_meters_per_unitOverride the source unit declaration when the STEP header is wrong or ambiguous. Known unit names include metre, centimetre, millimetre, inch, and foot; custom factors use meters per source unit.
StepReadOptionssource_up_axis, source_handednessDeclare the source coordinate basis before normalization. Defaults are STEP-style Z up and right handed.
StepReadOptionstarget_units, target_meters_per_unitNormalize the imported asset to a target unit by applying a root transform and updating the asset's declared units.
StepReadOptionstarget_up_axis, target_handednessNormalize the imported asset to a target up-axis or handedness. Import reports include the exact normalization transform and whether it changed the asset space.

STEP imports preserve negative-determinant transforms instead of silently rewriting geometry. When local or composed world transforms are mirrored, affected nodes get local_transform_mirrored / world_transform_mirrored metadata, the import report records mirrored_transforms counts, and a warning calls out that downstream normal or winding compensation may be required.

PmiAnnotationidStable annotation id used for references from parts or mesh groups.
PmiAnnotationkindAnnotation type such as dimension, datum, tolerance, note, or backend-specific kinds.
PmiAnnotationtextHuman-readable annotation text.
PmiAnnotationvalue, unitNumeric measurement value and unit when available.
PmiAnnotationtoleranceTolerance(upper=..., lower=...) values for dimensional or GD&T annotations.
PmiAnnotationapplies_toTarget ids such as part ids, node ids, face groups, edge groups, or material ids.
MetadataExportOptionsmodeExport metadata as full, count-only summary, or none.
MetadataExportOptionspmiExport PMI as none, summary, metadata, metadata_and_visuals, or full. metadata_and_visuals emits metadata records, stable links, and deterministic glTF/USD marker geometry with simple vector text glyphs; full AP242 visual presentation reconstruction remains planned.
asset.write_gltf(
    "motor.glb",
    options=fc.GltfExportOptions(
        metadata=fc.options.MetadataExportOptions(mode="full", pmi="metadata"),
    ),
)

asset.write_usd(
    "motor.usdc",
    options=fc.UsdExportOptions(
        metadata=fc.options.MetadataExportOptions(mode="full", pmi="metadata_and_visuals"),
    ),
)

BREP Healing

Run BREP healing before tessellation when STEP topology needs sewing, edge fixing, tolerance unification, or open-shell and unstitched-edge reporting.

asset = fc.read_step("motor.step").heal_brep(
    fc.options.BrepHealOptions(
        tolerance=0.05,
        group_open_shells=True,
        sew_faces=True,
        fix_edges=True,
        unify_same_domain=True,
        remove_overlapping_faces=True,
        overlap_area_ratio=0.995,
        remove_sliver_faces=True,
        max_sliver_area=1e-4,
        unify_tolerances=True,
        fail_on_open_shells=False,
    ),
    where=fc.Filter.path("*/Housing/*"),
)

Healing stores per-part brep_* metadata and records a heal_brep report step. You can also run it inside a one-shot conversion: fc.convert(..., heal_brep=fc.options.BrepHealOptions()).

What it does:

  • Open-shell grouping processes disconnected shell groups independently, so unrelated surface patches aren't forced through one global sewing pass.
  • Same-domain cleanup uses OCCT to merge neighboring faces/edges on coincident surfaces and curves.
  • Overlap cleanup triangulates faces, measures coplanar overlap against overlap_area_ratio, and removes redundant z-fighting faces with OCCT BRepTools_ReShape.
  • Sliver removal is requested through the backend; when unavailable it warns rather than claim the shape changed.

What it records:

  • Metadata — BREP kind; solid/shell/wire/edge/face counts; open shells; free/unstitched edges; small edges at or below tolerance; sliver counts; overlap and resolved-overlap counts; open-shell grouping counts; same-domain reductions.
  • tolerance_policy — effective source/local and target units, meters-per-unit conversions, tolerance and sliver area in metric units, and whether each cleanup stage is enabled, disabled, requested, or not implemented.
  • Warnings for remaining open shells, free edges, small edges, or unresolved overlapping face pairs.

Brep healing parameters:

ParameterMeaning
toleranceWorking tolerance used for sewing, edge fixes, and tolerance unification. Must be greater than zero.
group_open_shellsGroup disconnected open shell patches before running the BREP cleanup stack.
sew_facesAttempt to sew adjacent faces into shells before tessellation.
fix_edgesAttempt to repair bad trims and edge curves where supported by the backend.
unify_same_domainMerge neighboring faces/edges that lie on the same OCCT surface or curve domain.
remove_overlapping_facesRemove redundant coplanar faces whose projected overlap would cause z-fighting.
overlap_area_ratioMinimum overlap ratio against the smaller face before an overlapping face is removed.
remove_sliver_facesRequest tiny sliver-face removal before tessellation. Current backend support is limited and reports a warning when removal is unavailable.
max_sliver_areaArea threshold for sliver-face removal.
unify_tolerancesNormalize shape tolerances to the requested working tolerance.
fail_on_open_shellsRaise when healing detects open shells instead of reporting a warning.
whereOptional filter that limits healing to selected assembly occurrences.

Tessellation Controls

Tessellation supports global and per-part settings for edge limits, boundary preservation, curvature-adaptive OCCT meshing, material/metadata-driven detail adaptation, skinny-triangle cleanup, and per-part quality metrics.

asset = fc.read_step("motor.step").tessellate(
    fc.TessellationOptions(
        sag=0.05,
        sag_ratio=None,
        angle=10.0,
        min_edge_length=0.02,
        max_edge_length=2.0,
        max_polygon_length=4.0,
        preserve_boundaries=True,
        curvature_adaptive=True,
        detail_adaptive=True,
        avoid_skinny_triangles=True,
        quality_report=True,
        free_edge_report=True,
        reuse_existing_meshes=True,
        part_settings={
            "housing": {"sag": 0.03, "sag_ratio": 0.005, "max_edge_length": 1.0},
            "Fastener": {"sag": 0.15},
        },
    )
)

quality = asset.tessellation_quality_report()

part_settings keys match a part id or part name. A few behaviors worth knowing:

  • Quality reports (quality_report=True) record per-part edge length, triangle area, aspect ratio, skinny-triangle, duplicate-polygon, boundary-edge, and non-manifold-edge counts, plus advisories for coarse absolute sag, aggressive polygon-length limits, and shiny/high-detail/curved parts lacking sag_ratio or curvature_adaptive.
  • detail_adaptive=True turns shiny/high-detail material metadata and curved-BREP detection into per-part settings — affected parts get sag_ratio=0.01 (when unset) and curvature_adaptive=True. Explicit part_settings always win.
  • tolerance_policy on the report step (and on part/mesh metadata) records the active deflection kind, source/target units, meters-per-unit conversions, and converted unit-bearing values; it warns when unit normalization makes sag or max length suspiciously coarse or fine.
  • Risk metadata — parts record tessellation_face_groups, tessellation_estimated_draw_calls, and retained-patch counts; the step warns when retained patches, face groups, or material splits could raise submesh/draw-call/export pressure.
  • Provenancetessellation_attribute_sources records whether positions, triangles, normals, tangents, UVs, face groups, free-edge diagnostics, and BREP patches came from tessellation, an imported mesh, or weren't generated. tessellation_edge_control_passes is 2 only when a cleanup pass was rerun after the first edge-control pass changed the mesh.

Size-adaptive tessellation helpers can generate part_settings from part bounding-box diagonals, using existing mesh bounds or source BREP bounds when the OCCT backend is available:

asset = fc.read_step("motor.step")
tessellation = fc.profiles.size_adaptive_tessellation(
    asset,
    base=fc.TessellationOptions(sag=0.1, angle=15.0, quality_report=True),
    bands=(
        fc.profiles.TessellationSizeBand(max_diagonal=25.0, sag=0.02, angle=8.0, max_polygon_length=1.0),
        fc.profiles.TessellationSizeBand(max_diagonal=None, sag=0.12, sag_ratio=0.01, angle=18.0),
    ),
)
asset = asset.tessellate(tessellation)

Explicit part_settings on base remain authoritative; the helper only fills settings for parts that do not already have a part-id or part-name override.

Tessellation parameters:

ParameterMeaning
sagAbsolute chordal deviation between source surface and tessellated mesh. Set this to override the default relative strategy. Lower values produce more triangles.
sag_ratioRelative chordal deviation ratio. Bare TessellationOptions() defaults to 0.0002 so tessellation scales with part size.
angleAngular deviation limit in degrees. Lower values preserve curved surfaces with more triangles.
relativeCompatibility switch for interpreting sag as a relative backend deflection when sag_ratio is unset. Prefer sag_ratio for new relative-tolerance workflows.
min_edge_lengthCollapse or avoid edges shorter than this length during post-processing.
max_edge_lengthSplit long triangle edges to keep mesh density bounded. Very small values on non-elongated parts emit a quality advisory because this control is most useful for long objects with lighting artifacts.
max_polygon_lengthReport tessellated polygon edges longer than this threshold without subdividing geometry. Quality reports count these as long_edges; the tessellation step emits warnings when exceeded and advises when the limit is aggressive for ordinary parts.
preserve_boundariesPreserve CAD face and boundary edges during tessellation cleanup.
curvature_adaptiveRequest curvature-aware meshing from the backend when available.
detail_adaptiveAuto-apply finer per-part tessellation to shiny material, high-detail metadata, or curved BREP-face parts by enabling curvature-adaptive meshing and setting sag_ratio=0.01 when unset.
avoid_skinny_trianglesRun a cleanup pass that reduces long skinny triangles.
quality_reportRecord per-part tessellation quality metrics and advisories for later reporting. Coarse absolute sag relative to part size is flagged when relative=False and no sag_ratio is set; shiny, high-detail, or curved BREP parts also advise per-part sag_ratio or curvature_adaptive tuning.
free_edge_reportRecord free/boundary edge and non-manifold edge counts on tessellated parts and warn when free edges are present.
create_normalsGenerate normals during tessellation when the backend can provide them. Attribute provenance records tessellation, disabled, or missing for normals.
keep_brepKeep source BREP handles on parts after tessellation for later BREP-aware operations. When False, source BREP handles are dropped even when an imported mesh is reused instead of retessellated. Tessellated parts record brep_patch_cleanup=retained or deleted and warn when many retained patches could increase runtime/export risk.
reuse_existing_meshesReuse meshes already present on imported parts. Set to False to retessellate from source BREP where available.
max_triangles_per_partOptional guard that raises FascatError when any tessellated part exceeds the limit.
part_settingsPer-part overrides keyed by part id or part name. Supports the same tessellation option names.

sag and sag_ratio are mutually exclusive in the effective options. If you construct TessellationOptions(sag=0.1) and leave sag_ratio at its default, Fascat clears sag_ratio and uses the absolute tolerance. Pass sag_ratio=... without sag for relative tessellation.

Repair parameters:

ParameterMeaning
toleranceMerge tolerance for nearby vertices. 0.0 resolves to 1e-5 of the selected mesh bounding-box diagonal.
merge_verticesDeduplicate vertices after tessellation.
delete_degenerateRemove triangles with repeated vertices or near-zero area.
fix_windingNormalize triangle winding where a consistent orientation can be inferred, including inward closed components detected by signed volume.
quality_reportRun heavier before/after repair diagnostics for duplicate polygons, degenerate triangles, boundary and non-manifold edges, T-junctions, boundary gaps, and orientability. Defaults to False so conversion repair does geometric cleanup without paying for report-only topology scans.
face_orientationFace-orientation policy: exterior makes shared-edge winding consistent and flips inward closed components outward; single_sided_open_shell makes each orientable open-shell component shared-edge consistent; viewer_standpoint orients faces toward viewer_position; source_trusted and preserve keep source winding.
normal_orientationNormal-orientation policy: from_faces regenerates normals from repaired faces; viewer_standpoint orients generated normals toward viewer_position; source_trusted and preserve keep compatible source normals when possible.
viewer_positionThree-number viewer position required when either orientation policy is viewer_standpoint. Recorded in metadata and reports.
fill_small_holesFill small mesh boundary loops as a fallback mesh repair step. Fill faces inherit the material of the nearest neighboring face.
area_epsilonArea threshold used to classify degenerate triangles. Defaults to a scale-invariant value derived from the mesh bounding box (1e-12 × squared diagonal); pass a value to override.
jobsWorker count for independent mesh-bearing parts. 1 keeps serial behavior.

Repair always records orientation policy metadata (repair_face_orientation_strategy/_status, repair_normal_orientation_strategy/_status, and optional repair_orientation_viewer_position), so viewer-standpoint or source-trusted choices stay visible even when the backend preserves source orientation. The report step also records a unit-aware tolerance_policy covering merge tolerance, degenerate area epsilon, and the status of each cleanup stage.

With quality_report=True, repair adds before/after counts for duplicate polygons, degenerate triangles, boundary edges, non-manifold edges, T-junctions, boundary gaps, and orientability. Note:

  • Duplicate polygons are triangles referencing the same three vertices, regardless of winding.
  • T-junctions and boundary gaps are reported by default but only fixed when the opt-in fix_t_junctions / stitch_boundary_gaps flags are set — non-zero counts after a report-only repair emit a warning. Stitched vertices keep the attributes (normals, tangents, UVs) of the surviving representative vertex; when merged vertices disagreed on UVs, the count of conflicting merges is recorded as boundary_gap_stitching_uv_conflicts metadata.

MergeVerticesOptions gives you vertex merging as a standalone step (rather than the broad repair pass). tolerance=0.0 merges exact duplicate positions; larger values merge Euclidean-close positions, including across spatial bucket boundaries. By default normals, tangents, UVs, and material-boundary signatures are part of the merge key so hard edges and UV seams aren't collapsed — set preserve_normals=False, preserve_tangents=False, or preserve_uvs=False to drop that protection. Reports include removed counts, tolerance scale ratios (against bounding-box diagonal and shortest edge), high-risk-tolerance warnings, and tolerance_policy; quality_report=True (or --merge-vertex-quality-report) adds candidate counts and skipped-merge reasons. Use jobs to process independent parts concurrently.

Use DeleteDegeneratePolygonsOptions when you want Unity-style degenerate polygon cleanup as a standalone, reproducible step. area_epsilon controls the near-zero-area threshold, and delete_duplicates=True also removes exact duplicate polygons that reference the same three vertices regardless of winding. The operation always writes a report step, even when no polygons are removed, and per-part metadata records before/after degenerate and duplicate-polygon counts, removed triangle counts, removed unreferenced vertices, primary removal reasons for duplicate vertices, collapsed edges, near-flat area, and duplicate polygons, plus the unit-aware area threshold.

DeleteDegeneratePolygonsOptions parameters:

ParameterMeaning
area_epsilonArea threshold used to classify near-flat triangles as degenerate. Defaults to a bounding-box-derived, scale-invariant value.
delete_duplicatesRemove exact duplicate polygons after degenerate triangles are removed.

Feature-Preserving Simplification

Optimization can protect mechanical features while reducing triangle count. Preservation flags keep protected faces from being dropped when a target would otherwise remove them.

asset = asset.optimize(
    fc.OptimizeOptions(
        target_triangles=500_000,
        simplify=True,
        preserve_instances=True,
        preserve_hard_edges=True,
        hard_edge_angle=30.0,
        preserve_holes=True,
        preserve_material_boundaries=True,
        preserve_uv_seams=True,
        preserve_small_parts=True,
        small_part_triangle_threshold=64,
        preserve_silhouette=True,
    )
)

Protected-feature counts are stored as part metadata under simplification_preserved_features. Parts below small_part_triangle_threshold are left unsimplified when preserve_small_parts=True.

Optimization parameters:

ParameterMeaning
target_trianglesAbsolute triangle budget for selected geometry.
ratioFraction of original triangles to keep. Use this instead of target_triangles for proportional simplification.
preserve_instancesKeep repeated part instances sharing geometry instead of expanding them unnecessarily.
simplifyEnable triangle-count reduction. Disable to run only metadata and buffer optimization steps.
optimize_buffersReorder and compact mesh buffers after simplification.
preserve_hard_edgesProtect faces around hard normal edges from simplification.
hard_edge_angleEdge angle threshold in degrees used to detect hard edges.
preserve_holesProtect hole boundary loops and nearby faces.
preserve_material_boundariesAvoid collapsing across material boundaries.
preserve_uv_seamsAvoid collapsing across UV seams.
preserve_small_partsLeave small parts unsimplified instead of spending budget on them.
small_part_triangle_thresholdParts at or below this triangle count are treated as small when preservation is enabled.
preserve_silhouetteProtect bounding-box silhouette extremes to reduce visible shape loss.
jobsWorker count for independent mesh-bearing parts. 1 keeps serial behavior.

Hard-Edge Normals And Tangents

Staging can generate smooth, flat, or hard-edge normals and glTF-ready tangents. Smooth and hard-edge normals can use angle or area weighting. Hard-edge mode splits vertices across hard normal edges, material boundaries, and optional CAD face-group boundaries.

asset = asset.stage(
    fc.StageOptions(
        materials="cad",
        normals=True,
        normal_mode="hard_edges",
        normal_weighting="area",
        hard_edge_angle=45.0,
        preserve_face_boundaries=True,
        override_normals=True,
        tangents=True,
        tangent_uv_channel=0,
        override_tangents=False,
        validate_normals=True,
        uv0="box",
    )
)

Tangent generation needs the selected UV channel (tangent_uv_channel, default UV0). If tangents are requested without it, staging records missing-UV metadata and warns rather than silently writing none. Existing tangents are preserved unless staging invalidated them and the channel is still present; set override_tangents=True to force regeneration. When UV generation edits a mesh that already had tangents, the old basis is invalidated — staging regenerates from the selected channel if tangents=True, otherwise records the dropped state. glTF export writes a TANGENT attribute whenever staged meshes carry tangent data.

Normal and tangent parameters:

ParameterMeaning
normalsGenerate or preserve vertex normals. Automatically disabled when normal_mode="none".
normal_modesmooth averages face normals, flat keeps face normals, hard_edges splits vertices along hard edges, and none omits normals.
normal_weightingangle weights smooth or hard-edge normals by corner angle; area weights by triangle area and is the default.
hard_edge_angleEdge angle threshold in degrees for normal_mode="hard_edges". Defaults to 45.0.
preserve_face_boundariesTreat CAD face-group boundaries as hard normal boundaries.
override_normalsRegenerate existing normals. Set False to preserve existing normals and only generate normals when missing.
tangentsEnsure glTF-ready tangent vectors exist. Existing valid tangents are preserved by default.
tangent_uv_channelUV channel used when tangents need to be generated or regenerated. Defaults to 0.
override_tangentsRegenerate existing tangents instead of preserving them when tangents=True.
validate_normalsCheck for missing, zero-length, or invalid normals after staging.

Migration note: current staging defaults use normal_weighting="area" and hard_edge_angle=45.0. Assets re-converted from older defaults that used angle weighting and a 30 degree hard-edge threshold can shade or split vertices differently; pass the older values explicitly when visual parity matters.

UV And Material Pipeline

Staging can merge equivalent CAD materials, normalize simple CAD colors into PBR-friendly material values, tag UV unwrap settings, generate lightmap UV channels, and attach material-atlas metadata for later baking.

asset = asset.stage(
    fc.StageOptions(
        materials="cad",
        material_mode="pbr",
        merge_equivalent_materials=True,
        uv0="unwrap",
        uv1="lightmap",
        unwrap=fc.options.UnwrapOptions(
            texel_density=256.0,
            padding=4,
            max_stretch=0.15,
            method="conformal",
            iterations=32,
            tolerance=0.001,
            sharp_to_seam=True,
            forbid_overlapping=True,
        ),
        atlas=fc.options.AtlasOptions(
            enabled=True,
            max_size=4096,
        ),
        normalize_uvs=(1,),
    )
)

Atlas options on staging record texture-bake intent and layout limits. Dedicated material baking is the step that writes raster atlas images: baked maps are stored as first-class ImageResource objects, mirrored into material metadata for compatibility, and bound by the glTF/USD exporters as material textures.

When merge_equivalent_materials=True, staging groups materials by PBR factors after rounding base_color, metallic, roughness, and opacity to six decimal places. This absorbs floating-point noise from importers while keeping visibly distinct values separate; the precision is fixed.

Staging records detailed per-channel UV metadata (fields are prefixed uvN_, where N is the channel index):

  • Domains — UV0 defaults to the tileable domain (overlaps and coordinates outside 0..1 are fine). UV1 and lightmap channels use the bake domain, where overlaps, degenerate faces, or out-of-unit coordinates set uvN_validation_status and add warnings.
  • Layout quality — every channel records uvN_domain, uvN_bounds, uvN_validation_status, uvN_out_of_unit_vertices, uvN_degenerate_faces, and uvN_overlap_check. Expensive overlap checks run only for bake-domain UVs or when forbid_overlapping=True; skipped channels record uvN_overlap_pairs="not_evaluated".
  • Seam graph — duplicated-position UV discontinuities are reported per channel (uvN_seam_edges, uvN_seam_components, uvN_seam_length, …) and summarized on the asset as stage_uv_seam_graph_channels / stage_uv_seam_graph_edges.
  • Distortion — bake-domain channels, or any channel staged with max_stretch, record uvN_island_count, uvN_pack_efficiency, and angle/edge distortion fields; other channels record uvN_distortion_check="skipped".
  • Packingunwrap/lightmap bake channels are packed by xatlas with the configured padding/resolution and record uvN_pack_status, dimensions, and utilization. Bake-domain packing enforces a 2 px minimum; smaller requested padding values are clamped and reported as warnings.
  • Box projectionbox channels run an AABB projection and record uvN_projection_* fields for local/shared bounds, axes, destination, override policy, units, and resolved uv3d_size.

Two convenience modes: uv1="copy_uv0" reuses the UV0 layout for the secondary channel (warns if UV0 is missing), and normalize_uvs=(1,) rescales selected channels into 0..1 (warns if a requested channel is absent). Sharp-to-seam and forbid-overlap controls are recorded as intent and validated after generation, since the current xatlas path doesn't expose them directly.

asset = asset.stage(
    fc.StageOptions(
        uv0="box",
        aabb_projection=fc.options.AabbProjectionOptions(
            scope="shared",
            uv3d_size=1.0,
            override_existing=True,
        ),
    )
)

When uv0 or uv1 uses unwrap or lightmap, fascat uses the optional xatlas backend for flattening, packing, and padding. method, iterations, tolerance, sharp_to_seam, and forbid_overlapping still record Unity-style solver and policy intent when xatlas does not expose a direct equivalent; Fascat validates the generated UVs and reports the actual pack status instead of treating the request as silently honored.

Staging, UV, and material parameters:

OptionParameterMeaning
StageOptionsmaterialsMaterial source policy: cad preserves CAD materials, display creates display materials, and none omits materials.
StageOptionsmaterial_modecad keeps source-style materials. pbr normalizes simple CAD colors into PBR-friendly material values.
StageOptionsmerge_equivalent_materialsMerge materials with equivalent visual values to reduce material count.
StageOptionsuv0Primary UV channel mode: none, box, unwrap, or lightmap.
StageOptionsuv1Secondary UV channel mode. Commonly lightmap for baked lighting, or copy_uv0 to duplicate UV0 into UV1.
StageOptionsnormalize_uvsUV channels to rescale into 0..1 after generation/copy. Use explicitly because UV0 may intentionally tile outside 0..1.
StageOptionsunwrapUnwrapOptions used when a UV channel uses unwrap.
StageOptionsatlasAtlasOptions used to record atlas layout and baking intent.
StageOptionsaabb_projectionAabbProjectionOptions used when a UV channel uses box projection.
StageOptionsjobsWorker count for independent mesh-bearing parts. 1 keeps serial behavior.
AabbProjectionOptionsscopelocal projects each part against its own AABB; shared projects selected parts against one shared AABB.
AabbProjectionOptionsuv3d_sizeOptional real-world size per UV tile. When unset, UnwrapOptions.texel_density can derive the tile size; otherwise coordinates are normalized to the chosen AABB axes.
AabbProjectionOptionsoverride_existingReplace existing destination-channel UVs during box projection. Set False to preserve existing UVs and record that choice.
UnwrapOptionstexel_densityDesired texture density for generated UVs. For AABB projection, atlas.max_size / texel_density resolves the real-world size per UV tile when uv3d_size is unset.
UnwrapOptionspaddingPadding between UV islands in pixels.
UnwrapOptionsmax_stretchMaximum tolerated UV stretch before reporting unwrap risk.
UnwrapOptionsmethodRequested unwrap solver intent: default, conformal, or isometric. Non-default values are recorded as intent with the xatlas backend.
UnwrapOptionsiterationsRequested unwrap solver iteration budget. Recorded as intent until a backend exposes this control.
UnwrapOptionstoleranceRequested unwrap solver error threshold. Recorded as intent until a backend exposes this control.
UnwrapOptionssharp_to_seamRequest sharp edges as UV seams for unwrap/lightmap channels. Recorded as intent until a backend exposes explicit seam policy controls.
UnwrapOptionsforbid_overlappingRequire non-overlapping UV islands. When set explicitly, staging raises UVOverlapError if overlapping UV faces remain; bake-domain channels (UV1/lightmap) are always checked and warn loudly by default.
AtlasOptionsenabledRecord atlas metadata and prepare materials for later baking.
AtlasOptionsmax_sizeMaximum atlas texture size in pixels.

Scene Optimization

Use scene optimization to reduce draw calls after staging and optional hierarchy merging. It:

  • batches compatible meshes (optionally by material) and splits oversized merged meshes;
  • reconstructs repeated mesh instances — exact matches, or near-identical ones within a position tolerance — when vertex attributes, materials, and metadata match;
  • simplifies empty hierarchy and annotates the intended index-buffer width;
  • reports duplicate vertex/triangle counts, estimated payload-byte savings, and how mesh/material counts, submesh/material slots, instances, and merged batches contributed to the draw-call estimate.

When batching removes reusable instances, the report includes the same export advisor used by explicit merge operations.

asset = asset.optimize_scene(
    fc.options.SceneOptimizeOptions(
        batch_by_material=True,
        merge_compatible_meshes=True,
        split_large_meshes=True,
        max_vertices_per_mesh=65_535,
        index_buffer="auto",
        flatten="safe",
        remove_empty_nodes=True,
        instance_policy="auto",
        instance_similarity_tolerance=0.0,
    )
)

Scene optimization parameters:

ParameterMeaning
batch_by_materialGroup compatible geometry by material to reduce draw calls.
merge_compatible_meshesMerge meshes that can share buffers and material assignments safely.
split_large_meshesSplit merged output that exceeds the configured vertex limit.
max_vertices_per_meshVertex limit used for splitting and index-buffer planning.
index_bufferauto chooses 16-bit or 32-bit indices. uint16 and uint32 force a width.
flattennone preserves hierarchy, safe removes only safe empty structure, and all aggressively flattens.
remove_empty_nodesRemove hierarchy nodes with no part and no children.
instance_policyauto and preserve reconstruct exact repeated mesh instances when vertex attributes, material assignments, and metadata match. expand duplicates instances per occurrence.
instance_similarity_tolerancePosition tolerance for reconstructing near-identical repeated meshes with matching topology, vertex attributes, material assignments, and metadata. 0.0 keeps exact fingerprint matching only.

Optimization Actions

Use explicit optimization actions when a realtime pipeline needs named preparation steps and separate report entries for each action.

asset = asset.bake_materials(
    fc.options.BakeMaterialOptions(
        maps_resolution=2048,
        force_uv_generation=True,
        bake=("base_color", "opacity"),
    )
)

asset = asset.decimate(
    fc.DecimateOptions(
        criterion="target",
        target_triangles=250_000,
        surface_tolerance=0.1,
        line_tolerance=0.02,
        normal_tolerance=15.0,
        uv_tolerance=0.01,
        protect_topology=True,
        preserve_painted_areas=True,
        preserve_ambient_occlusion=True,
        budget_scope="selection",
        uv_importance="preserve_islands",
        cleanup_attributes=("unused_uvs", "tangents"),
        iterative_threshold=1_000_000,
    )
)

asset = asset.remove_holes(fc.options.RemoveHolesOptions(max_diameter=3.0, prefer_brep=True))
asset = asset.remove_occluded(fc.options.RemoveOccludedOptions(strategy="advanced", level="triangles"))
asset = asset.lods(
    fc.options.LODGeneratorOptions(
        preset="vr",
        levels=(
            fc.options.LODLevel(screen_coverage=0.5, target_ratio=0.5),
            fc.options.LODLevel(screen_coverage=0.2, target_ratio=0.25),
            fc.options.LODLevel(screen_coverage=0.05, target_ratio=0.1),
        ),
        validate=True,
    )
)

Material baking creates a shared flat material plus raster atlas images from selected maps and per-face assignments. Images are stored as ImageResource objects and bound by glTF/USD exports through texture slots or UsdUVTexture networks. When multiple source materials are collapsed into the baked output material, Fascat uses a simple arithmetic mean for base color, metallic, roughness, and opacity. The average is not currently weighted by face area, texel coverage, or material usage. ambient_occlusion_strategy selects conservative, exterior, or advanced direction sets when baking AO maps or protecting low-AO faces during decimation. Emissive bakes record baked_emissive_source plus material/fallback face counts so explicit material emission can be distinguished from the black fallback.

Hole removal uses mesh boundary classification and filling when BREP feature editing is unavailable. Occlusion removal uses deterministic visibility sampling; asset metadata records sample coverage, direction coverage, and a confidence score, and the report notes that thin occluders may need higher precision.

Decimation records what it did and how aggressively:

  • Strategytarget_strategy (and decimate_target_strategy metadata) marks the run as explicit target count, target ratio, or quality/error-hint simplification; decimate_requested_keep_ratio is recorded when derivable. Keeping under 20% of source triangles warns, since that suits distant LODs more than LOD0.
  • Memory & passes — a RAM estimate (Unity's ~5 GB per million source triangles), iterative_threshold control, and decimate_simplification_passes / decimate_iterative_passes / decimate_iterative_recommended.
  • Selection budgets — selection-wide runs record per-part target allocation (assigned targets, reduced-vs-preserved counts, min/max), showing which dense parts absorbed the reduction.
  • Protected features — counts for hard edges, hole boundaries, material boundaries, UV seams, and silhouette faces. preserve_painted_areas protects painted/protected/weighted/important face groups; preserve_ambient_occlusion protects low-AO faces.
  • UV handlinguv_importance is ignore (strip UV/tangents first), preserve_seams (use then strip), or preserve_islands (keep through output); cleanup_attributes removes unused UV channels/tangents before simplification.

LOD generation simplifies progressively — LOD1 from the source mesh, later levels from the previous LOD, each preserving its requested ratio against the source count. Parts without tessellated meshes are skipped (lod_status="skipped_no_mesh", plus lod_generated_parts / lod_skipped_no_mesh_parts on the asset). Reports record source/added/full-chain vertex/triangle counts and payload bytes, per-level provenance (instance reuse, material merge, texture bake, culling-granularity changes, resolved export representation), and chain advisories (more than four levels; over-aggressive LOD1/LOD2; geometry-only far LODs that should bake to one mesh/material). engine_profile="unity" exports LODs as MSFT_lod variant nodes; "unreal" exports separate _LOD# scene nodes for pipelines that ignore MSFT_lod.

Optimization action parameters:

OptionParameterMeaning
BakeMaterialOptionsmaps_resolutionRaster atlas texture size in pixels for generated baked maps.
BakeMaterialOptionslightmap_resolutionResolution used when generating or repacking bake/lightmap UVs. Defaults to 1024.
BakeMaterialOptionsforce_uv_generationGenerate UVs first when selected meshes do not have the required UV channel.
BakeMaterialOptionsuv_channelUV channel used for baking.
BakeMaterialOptionspaddingTexture padding between islands in pixels.
BakeMaterialOptionsbakeMaps to bake, such as base_color, opacity, normal, roughness, metallic, ao, or emissive.
BakeMaterialOptionsmerge_outputReplace selected materials with a shared baked output material.
BakeMaterialOptionsambient_occlusion_strategyDirection set for baked AO maps: conservative, exterior, or advanced.
DecimateOptionscriteriontarget prioritizes a triangle budget. quality passes the largest configured tolerance as a target-error hint to the simplification backend and records requested/result metadata. Reports use decimate_quality_bound_policy="hint" because the backend can exceed the hint instead of enforcing it.
DecimateOptionstarget_trianglesAbsolute triangle target for selected geometry. In the CLI, --decimate uses the selected profile or target-device triangle budget when no explicit target or ratio is supplied.
DecimateOptionstarget_ratioFraction of source triangles to keep when no absolute target is set. Ratios below 20% produce an LOD0 distortion warning.
DecimateOptionssurface_toleranceSurface tolerance input used by criterion="quality" to derive the simplification target error bound.
DecimateOptionsline_toleranceLine-feature tolerance input included in the quality target error bound.
DecimateOptionsnormal_toleranceMaximum normal deviation in degrees.
DecimateOptionsuv_toleranceUV tolerance input included in the quality target error bound.
DecimateOptionsprotect_topologyAvoid topology changes that would remove important boundaries. Reports include protected hard-edge, hole-boundary, material-boundary, UV-seam, silhouette, and total feature-face counts.
DecimateOptionspreserve_painted_areasPreserve face groups or metadata-marked face indices named as painted, protected, weighted, or important. Reports include painted-area and combined importance-face counts.
DecimateOptionspreserve_ambient_occlusionPreserve low-AO faces from the sampled AO estimator during decimation. Reports include ambient-occlusion and combined importance-face counts.
DecimateOptionsambient_occlusion_strategyDirection set for the low-AO estimator used by preserve_ambient_occlusion: conservative, exterior, or advanced.
DecimateOptionsbudget_scopepart budgets each part separately. selection uses a global selected-geometry target so sparse/simple parts can stay intact while dense parts absorb more reduction. Global selection decimation reports per-part target allocation, estimated RAM, and iterative-threshold status.
DecimateOptionsuv_importanceTexture-coordinate handling: preserve_islands keeps UVs, preserve_seams protects seam topology then drops UVs, and ignore strips UVs/tangents before decimation.
DecimateOptionscleanup_attributesPre-decimation cleanup for attribute streams that are not useful to simplification. unused_uvs removes empty, constant, or zero-area UV channels. tangents removes tangents before simplification. Reports record removed channels, removed tangent parts, preserved UV channels, and UV seam/island constraint status.
DecimateOptionsiterative_thresholdSource-triangle threshold above which decimation inserts intermediate simplification passes before the final target and reports actual pass counts.
DecimateOptionsjobsWorker count for independent mesh-bearing parts. 1 keeps serial behavior.
RemoveHolesOptionsthrough, blind, surfaceHole-type filters for boundary-loop classification. through matches paired aligned openings, blind matches open pocket mouths, and surface matches remaining surface openings.
RemoveHolesOptionsmax_diameterOnly fill detected open boundary loops at or below the measured planar-span diameter.
RemoveHolesOptionsprefer_brepRequest BREP-level feature removal. Current implementation warns and uses mesh boundary classification and filling.
RemoveOccludedOptionsstrategyVisibility direction set: conservative checks cardinal views, exterior adds exterior diagonals, and advanced uses the densest deterministic direction set.
RemoveOccludedOptionslevelRemoval granularity: parts removes fully hidden occurrences, submeshes removes fully hidden material groups, and triangles removes hidden faces.
RemoveOccludedOptionsprecisionMaximum part-level face sample count before deterministic downsampling. Higher values can help thin occluders and large parts.
RemoveOccludedOptionshemi_evaluationRestrict visibility rays to the upper hemisphere and side views for top/side-oriented evaluation.
RemoveOccludedOptionsneighbors_preservationKeep this many rings around visible triangles to reduce cracks.
RemoveOccludedOptionsconsider_transparency_opaqueTreat transparent materials as opaque for conservative visibility.
RemoveOccludedOptionspreserve_cavitiesPreserve interior cavities above the configured volume threshold.
RemoveOccludedOptionsminimum_cavity_volume_m3Cavity volume threshold used when preserve_cavities=True.
LODGeneratorOptionspresetDefault LOD level set: desktop, web, mobile, or vr.
LODGeneratorOptionslevelsExplicit LODLevel entries overriding the preset.
LODGeneratorOptionsvalidateValidate monotonic triangle, material, and draw-call counts after generation.
LODGeneratorOptionsoutputLOD representation: variants, extras, or separate.
LODGeneratorOptionsallow_non_monotonicPermit non-monotonic LODs without failing validation.
LODOptionsengine_profileSwitch-distance and glTF export profile: generic, unity, or unreal. unity resolves to MSFT_lod variant export; unreal resolves to separate _LOD# scene nodes for import tools that ignore MSFT_lod.
LODOptionsswitch_distance_overridesOptional per-level switch distances. Use None for levels that should keep the profile formula.
LODOptionsfar_lod_bakeFor far-distance levels, collapse material indices to a one-material far LOD policy and record far texture-bake metadata.
LODOptionsscene_far_proxyBuild an optional scene-level far proxy part from the final LOD occurrence geometry as one mesh, one material, and one draw-call proxy. glTF export attaches it as root MSFT_lod metadata.
LODOptions / LODGeneratorOptionsjobsWorker count for independent mesh-bearing parts. 1 keeps serial behavior.
LODLevelscreen_coverageScreen fraction at which this LOD becomes appropriate.
LODLeveltarget_ratioFraction of source triangles to keep for this LOD.
LODLevelswitch_distance_overrideExplicit switch distance for this level when using run_lod_generators().
LODOptions / LODGeneratorOptionsreport metadataLOD steps record lod_source_*, newly generated lod_added_*, imported lod_retained_*, and full lod_chain_* counts for vertices, triangles, and estimated mesh payload bytes, plus per-level vertex/triangle counts, simplification source, omitted tiny-part LOD counts, instance-reuse counts, material-merge counts, texture-bake counts, culling-granularity change counts, scene-far-proxy counts, resolved export mode, and LOD chain advisory counts/codes.

Switch distances are derived from each part's bounding-box diagonal unless an override is supplied: generic = diagonal / screen_coverage, unity = diagonal / (2 * screen_coverage), and unreal = diagonal / sqrt(screen_coverage). The resolved values are recorded as lod_switch_distance and lod_level_switch_distances; the source is recorded as formula or override.

Occlusion metadata includes occlusion_candidate_count, occlusion_face_count, occlusion_sample_count, occlusion_visible_sample_count, occlusion_hidden_sample_count, occlusion_sample_coverage, occlusion_direction_coverage, and occlusion_confidence. The confidence score is the lower of sample coverage and direction coverage; lower values mean the result depends on sparse sampling or a reduced direction set.

Report examples for destructive and approximate operations:

{
  "name": "merge",
  "before": {"parts": 42, "triangles": 120000, "draw_calls": 42},
  "after": {"parts": 8, "triangles": 120000, "draw_calls": 8},
  "warnings": []
}
{
  "name": "bake_materials",
  "before": {"materials": 12, "draw_calls": 18},
  "after": {"materials": 1, "draw_calls": 1},
  "warnings": []
}
{
  "name": "remove_holes",
  "before": {"triangles": 8400},
  "after": {"triangles": 8412},
  "warnings": [
    "BREP feature-level hole removal is not implemented; using mesh boundary classification and fill"
  ]
}
{
  "name": "remove_occluded",
  "before": {"parts": 120, "triangles": 300000},
  "after": {"parts": 118, "triangles": 296000},
  "warnings": [
    "remove_occluded uses deterministic sampled visibility; thin occluders may require higher precision"
  ]
}

One-shot conversion

Use fc.convert() when you want the full default pipeline and output validation in one call.

import fascat as fc

asset = fc.convert(
    "motor.step",
    "motor.usdc",
    profile="realtime-desktop",
    max_triangles=500_000,
    where=fc.Filter.path("*/Fasteners/*"),
    merge=fc.MergeOptions(mode="by_material", metadata="combine"),
)

print(asset.stats())
print(asset.report.summary())

The output format is selected from the output suffix:

fc.convert("motor.step", "motor.usdc")
fc.convert("motor.step", "motor.usda", debug=True)
fc.convert("motor.step", "motor.glb", profile="virtual-reality")
fc.convert("motor.step", "motor.glb", profile="realtime-mobile")
fc.convert("motor.step", "motor.glb", profile="mixed-reality")
fc.convert("motor.step", "motor.gltf", profile="realtime-web", max_triangles=120_000)
fc.convert("legacy.igs", "legacy.glb")
fc.convert("native.brep", "native.usdc")

fc.convert() validates generated output by default. Pass validate_output=False only when another step in your pipeline validates the asset. When where is provided to fc.convert(), tessellation, repair, and staging still run for the full asset, while standalone vertex merging, standalone degenerate-polygon cleanup, hierarchy merge, scene optimization, optimization actions, optimization, and LOD generation are scoped to the matched assembly subset.

Conversion parameters:

ParameterMeaning
input_pathCAD input path ending in .step, .stp, .igs, .iges, or .brep; Python callers may pass a sequence of STEP paths for explicit multi-root import, and the CLI accepts repeated --input STEP roots. CLI stdin remains STEP-oriented because stdin has no suffix.
output_pathOutput path. Suffix selects USD, glTF, OBJ, STL, or FBX.
profileBuilt-in profile name (inspect-only, realtime-desktop, realtime-web, realtime-mobile, virtual-reality, augmented-reality, mixed-reality) or a ConversionProfile that supplies default tessellation, repair, stage, optimize, LOD, budget, and workflow-recipe metadata.
tessellation_sag, angle, max_triangles, lod_ratiosProfile override keywords for built-in realtime profiles. Realtime profiles use sag_ratio=0.0002 by default; pass tessellation_sag only to switch that profile to an absolute sag tolerance. Overrides are applied while constructing the named profile, before explicit option objects such as tessellation=... or optimize=... override individual pipeline steps.
import_optionsStepReadOptions for STEP metadata and PMI import.
tessellationOverrides the profile tessellation step.
heal_brepOptional BREP healing step before tessellation.
stageOverrides the profile staging step.
merge_verticesOptional standalone vertex merge step after staging.
delete_degenerate_polygonsOptional standalone degenerate-polygon cleanup step after vertex merging.
merge, explode, replaceOptional hierarchy operations run after staging.
sceneOptional scene optimization step.
bake_materials, remove_holes, remove_occluded, decimate, lod_generatorOptional explicit optimization actions.
optimizeOverrides the profile simplification step.
lodsOverrides the profile ratio-based LOD step.
progressCallback receiving (step_name, stats) after major conversion steps.
validate_outputReopen and validate generated output before returning. Defaults to True.
debugPrefer debuggable USDA conventions. Only valid for .usd or .usda outputs.
gltf_options, usd_options, obj_options, stl_options, fbx_optionsFormat-specific write options.
pipelinePipelineSpec loaded from TOML. When present, ordered pipeline steps drive the conversion.
whereOptional Filter applied to scoped hierarchy, optimization, and LOD steps.

For multiple branch-specific steps, load the same TOML pipeline format used by fascat convert --pipeline:

pipeline = fc.PipelineSpec.from_file("realtime.toml")
for advisory in pipeline.advisories():
    print(advisory["message"])
asset = fc.convert("motor.step", "motor.glb", pipeline=pipeline)

Pipeline files can also define import and export metadata policy:

[import]
metadata = "full"
pmi = true
design_variants = false
design_variant_selection = []
existing_meshes = true
multi_file = false
material_library_paths = ["vendor-materials.json"]
delete_free_vertices = false
delete_lines = false
construction_curve_policy = "preserve_metadata"
construction_curve_tube_radius = 0.01
target_units = "metre"
target_up_axis = "Y"
target_handedness = "right"

[export]
metadata = "summary"
pmi = "metadata"

Runtime Export Options

glTF and USD exports accept runtime delivery options, and OBJ/STL/FBX are available for mesh and DCC handoff workflows.

asset.write_gltf(
    "motor.glb",
    options=fc.GltfExportOptions(
        preset="web",
        quantize=True,
        meshopt=True,
        draco=False,
        texture_compression=None,
        texture_fallback_format="auto",
        png_compression=6,
        jpeg_quality=85,
        file_size_budget_mb=50,
        size_ladder=True,
        metadata=fc.options.MetadataExportOptions(mode="summary", pmi="metadata"),
    ),
)

asset.write_usd(
    "motor.usdz",
    options=fc.UsdExportOptions(package="usdz", file_size_budget_mb=100),
)

asset.write_obj("motor.obj", options=fc.options.ObjExportOptions(materials=True, write_mtl=True))
asset.write_stl("motor.stl", options=fc.options.StlExportOptions(binary=True, merge=True))
asset.write_fbx("motor.fbx", options=fc.options.FbxExportOptions(materials=True, normals=True, uvs=True))

Presetspreset="web" (also mobile, desktop, vr, ar) resolves to concrete compression defaults and, during fc.convert(), runs texture resize/dedupe cleanup with the preset's texture cap before writing.

Geometry compression — these can be combined:

  • quantize=True writes KHR_mesh_quantization accessors and composes the dequantization transform into referencing nodes.
  • meshopt=True writes EXT_meshopt_compression payloads, keeping fallback buffer data for loaders that ignore the extension.
  • draco=True runs the glTF Transform Draco encoder and writes KHR_draco_mesh_compression.

Texturestexture_compression="ktx2"/"basisu" runs the KTX2/Basis encoder and writes KHR_texture_basisu. Otherwise texture_fallback_format sets PNG/JPEG policy: auto keeps alpha-bearing sets PNG and color-only sets JPEG; explicit png or jpeg forces a format (png_compression/jpeg_quality tune it). Scalar transparency uses effective opacity without double-counting duplicated CAD alpha.

Draco export requires the gltf-transform CLI on PATH or FASCAT_GLTF_TRANSFORM. KTX2/Basis texture export requires Node.js plus @gltf-transform/core, @gltf-transform/extensions, ktx2-encoder, and sharp installed in the working directory or FASCAT_NODE_MODULE_ROOT.

Report fields — glTF write steps include runtime_dependencies (emitted/required extensions, extras.fascat, a runtime_compatibility matrix for glTFast/web/mobile/XR, and a runtime_decision_matrix). With size_ladder=True, a gltf_size_ladder step writes temporary baseline/quantized/meshopt/Draco/texture-compressed/requested GLB variants and records measured bytes plus unavailable-encoder warnings. All write steps record output size, estimated geometry/texture/metadata bytes, material/image counts, and budget warnings.

USDZ is built by writing a temporary USD stage and packaging it. glTF, USD, and OBJ exports write only referenced materials (the in-memory asset is unchanged); glTF also drops images used only by unused materials and reuses repeated embedded texture URIs. USD prim names are sanitized for identifier safety; when two sanitized names collide, the exporter assigns deterministic _2, _3, ... suffixes and keeps original node, part, and material identifiers in Fascat customData.

OBJ export writes vertex positions, normals, f v//vn face references, material assignments, and smoothing directives. Staged smooth normals export with smoothing enabled; flat, hard-edge, or generated face normals export with smoothing disabled.

FBX export writes ASCII FBX 7.4 files with Model, Geometry, Material, GlobalSettings, and Connections sections. Geometry uses FBX polygon-end index bits, preserves hierarchy transforms, and can write normal, tangent, UV, and per-face material layers. PBR material factors are approximated through legacy Phong properties, including effective material opacity. CreationTime is fixed to the Unix epoch so repeated exports are byte-stable for reproducible builds and golden-file tests.

Export option parameters:

OptionParameterMeaning
GltfExportOptionspresetNamed glTF export preset: desktop, web, mobile, vr, or ar. Presets request quantization, meshopt, KTX2/Basis texture compression, fallback quality, and fc.convert() texture resize/dedupe cleanup.
GltfExportOptionsquantizeWrite KHR_mesh_quantization accessors and dequantization transforms.
GltfExportOptionsmeshoptWrite EXT_meshopt_compression payloads with fallback uncompressed data.
GltfExportOptionsdracoRun Draco geometry compression and require KHR_draco_mesh_compression when mesh payloads are present.
GltfExportOptionsdraco_compression_levelDraco compression level, 0 (fastest) to 10 (smallest). Default 5 matches the encoder default.
GltfExportOptionsdraco_quantize_position / draco_quantize_normal / draco_quantize_texcoord / draco_quantize_colorPer-attribute Draco quantization bits (1-30). Defaults 14/10/12/8 match the encoder defaults.
GltfExportOptionsktx2_qualityKTX2/Basis encoder quality level (0-255), default 128.
GltfExportOptionsktx2_effortKTX2/Basis encoder compression effort (0-6), default 2.
GltfExportOptionsktx2_uastcForce UASTC (True) or ETC1S (False); None derives from texture_compression.
GltfExportOptionstexture_compressionRun KTX2/Basis texture compression for referenced texture images: ktx2 or basisu.
GltfExportOptionstexture_fallback_formatPNG/JPEG fallback policy when KTX2/Basis compression is not requested: auto, png, or jpeg. auto keeps alpha-bearing texture sets PNG-safe and color-only sets JPEG-compatible.
GltfExportOptionspng_compressionPNG fallback compression level, 0 through 9.
GltfExportOptionsjpeg_qualityJPEG fallback quality, 0 through 100. Reports warn when explicit JPEG fallback would discard alpha-bearing texture data.
GltfExportOptionsfile_size_budget_mbAdd report warnings when the output exceeds this size.
GltfExportOptionssize_ladderAdd a measured gltf_size_ladder report comparing temporary baseline, optimized, compressed, and requested GLB variants.
GltfExportOptionsmetadataMetadataExportOptions controlling metadata and PMI in extras.fascat.
UsdExportOptionspackagedefault writes normal USD. usdz writes a packaged .usdz file.
UsdExportOptionslayoutinstanced authors prototypes with internal references, LOD variant sets, and instancing; flat inlines each occurrence's full-detail mesh for viewers without USD composition support (e.g. three.js USDLoader); auto (default) resolves to flat for the realtime-web profile and instanced otherwise.
UsdExportOptionsfile_size_budget_mbAdd report warnings when the output exceeds this size.
UsdExportOptionsmetadataMetadataExportOptions controlling USD custom data and PMI prims.
ObjExportOptionsmaterialsWrite OBJ usemtl assignments when material data exists.
ObjExportOptionswrite_mtlWrite an .mtl sidecar next to the OBJ.
ObjExportOptionspreserve_groupsWrite OBJ group/object names from Fascat hierarchy and parts.
ObjExportOptionsfile_size_budget_mbAdd report warnings when the output exceeds this size.
StlExportOptionsbinaryWrite binary STL when True; ASCII STL when False.
StlExportOptionsmergeMerge selected triangles into one STL stream. STL does not preserve hierarchy or materials.
StlExportOptionsfile_size_budget_mbAdd report warnings when the output exceeds this size.
FbxExportOptionsmaterialsWrite FBX material nodes, per-face material indices, and model-material connections.
FbxExportOptionsnormalsWrite FBX normal layers.
FbxExportOptionstangentsWrite FBX tangent layers when mesh tangents exist.
FbxExportOptionsuvsWrite FBX UV layers when mesh UV channels exist.
FbxExportOptionsfile_size_budget_mbAdd report warnings when the output exceeds this size.

Profiles

Profiles provide practical defaults for tessellation, staging, optimization, LODs, and platform budget checks.

profile = fc.profiles.realtime_web(
    angle=20.0,
    max_triangles=250_000,
    lod_ratios=(0.5, 0.25),
)

asset = fc.convert("motor.step", "motor.glb", profile=profile)

Available profiles:

ProfileUseTarget FPSTriangle budgetFile-size budgetPer-mesh vertex budgetTexture resolution budgetTexture memory budgetLoad-time budgetDraw-call budgetUnity reference range
inspect-onlyinspect STEP input without conversionunsetunsetunsetunsetunsetunsetunsetunsetunset
realtime-desktophigher-detail OpenUSD or glTF output601,000,000200 MiB65,5354,096px512 MB2,000 ms2,00010M-100M triangles, under 10,000 draw calls
realtime-weblower triangle budgets for web delivery60250,00050 MiB65,5352,048px128 MB3,000 ms500100K-1M triangles, under 200 draw calls
realtime-mobiletighter mobile runtime budget for app-store builds60150,00050 MiB65,5352,048px128 MB2,500 ms250100K-500K triangles, under 1,000 draw calls
virtual-realitybalanced triangle budgets and LODs for VR runtimes90500,000100 MiB65,5352,048px256 MB1,500 ms250500K-2M triangles, under 1,000 draw calls
augmented-realitystricter phone and tablet AR runtime budget60100,00025 MiB65,5351,024px64 MB1,500 ms15050K-250K triangles, under 500 draw calls
mixed-realitystricter headset budget for mixed-reality runtimes6075,00025 MiB65,5351,024px64 MB1,200 ms10050K-200K triangles, under 500 draw calls

Pass either a profile name or a ConversionProfile from fc.profiles. Built-in profiles carry a WorkflowRecipe naming the target (web-glb, mobile-glb, vr-glb, high-fidelity-desktop), surfaced as a workflow_recipe report step that marks each stage honored, disabled, metadata_only, or unsupported. Realtime profiles default to relative tessellation with sag_ratio=0.0002; pass tessellation_sag=... when a target device profile needs an absolute tolerance. They also enable staging atlas metadata by default, with atlas.max_size matched to the profile's texture-resolution budget. Profile file-size budgets feed conversion reporting and validate --profile; explicit file_size_budget_mb export options and validate --max-file-size-mb take precedence.

When the profile has a budget, conversion reports add:

  • profile_budget — target FPS plus file-size, triangle, vertex, per-mesh vertex, texture-resolution, texture-memory, load-time, and draw-call budgets, draw-call breakdown, compression/extension caps, and Unity reference ranges. Fascat's defaults are intentionally stricter than Unity's broad ranges. Load time is a deterministic estimate (file size, geometry/texture bytes, draw-call overhead), not a measured runtime.
  • texture_export_policy (when baked textures are referenced, before write) — source/referenced/unused texture-set and map counts, largest resolutions, estimated bytes, the profile's texture caps, resize candidates and estimated savings, KTX2/Basis request state, and PNG/JPEG fallback policy with transparency-loss warnings.

Custom target-device profiles can overlay a budget on any built-in base profile:

name = "factory-tablet-ar"

[budget]
target_fps = 60
max_triangles = 42000
max_file_size_mb = 25
max_texture_resolution = 512
max_draw_calls = 120
supported_compression = ["meshopt"]
supported_runtime_extensions = ["KHR_mesh_quantization", "EXT_meshopt_compression"]
unity_reference_profile = "tablet-ar"
unity_reference_triangles = [30000, 60000]
profile = fc.profiles.from_file("factory-tablet.toml", base="realtime-mobile")
asset = fc.convert("motor.step", "motor.glb", profile=profile)

The CLI equivalent is `fascat convert motor.step motor.glb --profile realtime-mobile --target-device-profile factory-tablet.toml`. These TOML/JSON files are **budget overlays only** — tessellation, repair, staging, and LOD defaults still come from the base profile. Notes:

  • An overridden max_triangles becomes the optimization target (and the explicit decimation target when --decimate is used without --target-triangles/--ratio); max_vertices defaults to 3× the triangle budget unless set.
  • supported_compression and supported_runtime_extensions are optional caps; the profile budget report records a violation when the write emits anything outside them.

Reports and stats

Every imported or converted asset carries a report.

asset = fc.convert("motor.step", "motor.usdc")

print(asset.stats(include_lods=True))
print(asset.report.summary())

for step in asset.report.steps:
    print(step.name, step.duration, step.before, step.after)

asset.report.write_json("report.json")

The report records options, before/after counts, warnings, errors, and timings for each pipeline step. Approximate operations attach the limitation to the step that produced it, so you can tell exact geometry changes from fallbacks or metadata-only intent. Conversion reports add four framing steps:

  • preflight (before expensive work) — checklist warnings for missing patch cleanup, orientation prep, UV/tangent ordering, AO-bake UV1 prerequisites, and LOD0 optimization, plus glTF compression notes.
  • workflow_recipe — for built-in profiles, the target recipe and honored/disabled/metadata-only/unsupported choice counts.
  • conversion_manifest — the resolved profile, import options, operation settings, and export options.
  • workflow_summary — preparation stages (import cleanup, UV prep, baking, LOD, export compression, export) mapped to run/skipped status.

Use Asset.analyze() when you need geometry quality risks beyond raw part and triangle totals.

report = asset.analyze(
    fc.options.AnalyzeOptions(
        non_manifold_edges=True,
        open_boundaries=True,
        self_intersections=True,
        sliver_triangles=True,
        tiny_parts=True,
        draw_call_estimate=True,
        visual_risk=True,
    )
)

print(report.summary)
report.write_json("quality-report.json")

The analysis report includes per-part topology counts, actual triangle self-intersection counts, degenerate and sliver triangle stats, tiny-part stats, material count, draw-call estimate, draw-call breakdown fields, and visual-risk warnings derived from mesh quality and before/after pipeline report steps. Self-intersection checks ignore adjacent triangles that share vertices. Coplanar overlaps count as intersections, while point-only endpoint contact does not. If max_self_intersection_pairs is reached, self_intersections_lower_bound is true and the report includes self_intersection_pairs_checked and self_intersection_pair_limit; self_intersection_warnings is kept as a compatibility alias for self_intersections.

Analysis parameters:

ParameterMeaning
non_manifold_edgesCount edges shared by more than two triangles.
open_boundariesCount boundary loops and boundary edges.
self_intersectionsRun bounded triangle-triangle intersection checks and report detected self-intersections.
sliver_trianglesReport degenerate and high-aspect-ratio triangles.
tiny_partsReport parts below the configured diagonal threshold.
draw_call_estimateInclude estimated draw calls, mesh count, referenced material count, submesh/material slots, instances, and merged batch counts.
visual_riskEnable risk-oriented warnings from geometry quality and report steps.
sliver_aspect_ratioAspect-ratio threshold used to classify sliver triangles.
degenerate_area_epsilonTriangle area threshold used to classify degenerates. Defaults to a bounding-box-derived, scale-invariant value.
tiny_part_diagonalBounding-box diagonal threshold used to classify tiny parts.
max_self_intersection_pairsMaximum non-adjacent triangle pairs to check before reporting a lower-bound result.

Use the visual preview helpers when you need stable review artifacts in CI or before handing an asset to a runtime viewer:

from fascat import validation

preview = validation.write_preview(asset, "preview.png")
comparison = validation.write_before_after_previews(before_asset, after_asset, "visual-review/")
lod_contact_sheet = validation.write_lod_switch_previews(asset_with_lods, "lod-previews/")
turntable = validation.write_turntable_previews(
    asset,
    "turntable-views/",
    turntable=validation.TurntableOptions(views=8, elevations=(-30.0, 30.0)),
    baseline_dir="reference-views/",
)
diff = validation.compare_images("baseline.png", "preview.png", validation.VisualDiffOptions(pixel_tolerance=2))
suite = validation.write_runtime_parity_suite("runtime-parity/")
captures = validation.capture_runtime_parity_suite(
    "runtime-parity/",
    targets=("browser", "unity"),
    unity_command="Unity",
    promote_goldens=True,
    require_goldens=False,
)

The preview renderer is a local orthographic software renderer: it writes PNGs, uses material base colors, respects node transforms, and can substitute each part's LOD mesh into an LOD-switching contact sheet. write_turntable_previews() (and its file-path counterpart write_output_turntable_previews()) renders a grid of azimuth × elevation views with deterministic names such as az045_el+30.png plus a turntable.png contact sheet, and can diff every view against a same-named baseline directory in one call. Framing auto-fits each view, so turntable diffs detect silhouette and shading changes rather than absolute size changes. It is repeatable for a fixed Python, Pillow, and platform stack, but antialiasing and resampling can vary across platform builds, so CI baselines should compare with explicit thresholds. compare_images() is a general image-diff primitive that reports mean absolute error, max channel error, changed-pixel counts and ratio, and whether configured thresholds passed.

Validation

Direct write calls produce files but do not automatically reopen and validate them. Validate direct writes explicitly when you need the same safety as fc.convert(). fc.validate_output dispatches by suffix; per-format validators live on the io modules, and the runtime and visual preview machinery lives in fascat.validation — one import surface for everything measurement-related.

from fascat import validation
from fascat.io.gltf import validate_gltf
from fascat.io.usd import validate_usd

asset.write_usd("motor.usdc")
usd_stats = validate_usd("motor.usdc")

asset.write_gltf("motor.glb")
gltf_stats = validate_gltf("motor.glb")

stats = fc.validate_output("motor.glb")

runtime = validation.measure_browser_runtime(
    "motor.glb",
    options=validation.RuntimeBrowserOptions(duration_seconds=2.0),
)

preview = validation.write_output_preview("motor.glb", "motor-preview.png")
browser_preview = validation.write_browser_render_preview("motor.glb", "motor-browser.png")

The CLI can write a validation-time quality report for exported assets:

fascat validate motor.glb \
  --filter 'material=Painted*' \
  --geometry-quality \
  --report quality-report.json

fascat validate motor.glb --runtime-browser

fascat validate motor.glb \
  --runtime-browser-preview motor-browser.png \
  --visual-preview motor-preview.png \
  --lod-preview-dir lod-previews/

Validation-time geometry reports use the same filter selectors as conversion when an exported format can be reconstructed for analysis.

Software preview (always available). --visual-preview writes a stable PNG from the validated output mesh; --visual-baseline diffs it against a baseline and exits non-zero when thresholds fail. --lod-preview-dir writes lod0.png, each LOD level, and a lod-switching.png contact sheet (Fascat GLB exports preserve enough LOD metadata to reconstruct these).

Browser (glTF/GLB). --runtime-browser launches a local Chromium-compatible browser, runs a bounded WebGL workload, and reports load time, FPS, frame count, memory, and workload scale. --runtime-browser-preview renders a real WebGL screenshot — node transforms, base-color factors, quantized attributes, Draco (via an installed glTF Transform CLI), meshopt (fallback or local meshoptimizer), KTX2/Basis (Python alktx2 with the ktx2 extra installed, else installed glTF Transform + KTX-Software), and base-color textures. Decoded payloads are listed in decoded_extensions; if Draco/meshopt tooling is missing the preview is unsupported (no misleading image), and missing KTX2/Basis tooling falls back to status="rendered_partial". Set FASCAT_BROWSER or --runtime-browser-command if the browser isn't on PATH; with no browser, the report is status="unavailable" rather than an estimate.

Sparse accessors remain open.

Inspecting assets

Use to_dict() for structured inspection or JSON serialization.

asset = fc.read_step("motor.step")

print(asset.part_count)
print(asset.material_count)
print(asset.occurrence_count)

payload = asset.to_dict()
print(payload["root"])
print(payload["parts"])

The asset model preserves hierarchy, part records, material records, transforms, units, and source metadata where the STEP backend can read them.

glTF notes

OpenUSD is the highest-fidelity export path for USD-style LOD variants and instance metadata.

glTF export writes valid glTF 2.0 files for runtime use:

  • .gltf uses embedded binary buffers
  • .glb writes a binary glTF container
  • geometry is exported in metres and Y-up
  • original units and source up-axis are preserved in top-level Fascat extras
  • material subsets are exported as separate glTF primitives
  • generated LOD meshes are included as Fascat extras; Unity-profile exports add node-level MSFT_lod references with MSFT_screencoverage hints, and Unreal-profile exports add separate _LOD# scene nodes
  • write reports include runtime compatibility notes for KHR_mesh_quantization, EXT_meshopt_compression, KHR_draco_mesh_compression, KHR_texture_basisu, MSFT_lod, and extras.fascat