Skip to content

Solid models with metadata - #328

Open
parrangoiz wants to merge 70 commits into
mainfrom
arrangop/new-sim-interface
Open

parrangoiz wants to merge 70 commits into
mainfrom
arrangop/new-sim-interface

Conversation

@parrangoiz

@parrangoiz parrangoiz commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

This PR introduces a new solid model rendering pipeline that enables marking special objects in a model -- such as terminals or ports -- and annotating them with metadata. The marked objects are isolated into dedicated physical groups (PGs) and the metadata is returned by render! in a structured format that can later be used to programmatically generate Palace configurations. By design, the metadata is purely semantic and geometric, and does not include "physics configuration" such as material properties or port impedances. This enables a clean split between CAD+mesh generation and downstream configuration of any Palace simulation from a single source mesh.

To enable this, I've added a few new concepts to the public API (all encapsulated in an opt-in module):

  • SourceLayer. This is a natural extension of SemanticMeta: it has a name and a level, but also includes a material class (metal, dielectric, or null), a level, and a thickness. It corresponds naturally to a grouping of objects that can be added or removed by a single fabrication step, e.g. when depositing a film or etching pockets. Each source layer can be configured to appear or not appear in its eventual solid model or GDS destination (for the latter, an explicit GDSMeta can be passed). The "source" qualifier refers to the fact that these are the layers into which we'll place! entities -- subsequent CAD kernel operations can create new ones, which we refer to as "generated" layers.
  • SourceStack. The set of all SourceLayers in a model, keyed by their name, plus a map of level indices to z-coordinates.
  • LayerRef. A simple Symbol wrapper (and subtype of DeviceLayout.Meta) used to signal which SourceLayer we're placing an entity into.
  • Locator. A point-like GeometryEntity that can be place!d to mark special objects for selection. The entity or set of entities selected is that which contains the point.
  • LocatorMeta. An abstract type for all locator metadata. For now, its concrete types are Terminal, Ground, LumpedPort, WavePort, and Tag. The type of LocatorMeta used to place! a Locator determines the type of selection that is created. In the case of Terminal and Ground, the full connected component of metallic entities is selected.
  • A complete set of typed operations: Extrude, Cut, Fuse,
    Intersect, GetBoundary, GetInterface, Translate, Revolve, Hollow, Remove,
    RestrictTo, and SetPeriodic. These operate on layers (source or generated) and are lowered to PG-level operations by a simple compiler, which keeps track of layer dimensions, absorbs adjacent Remove operations ( simple peephole optimization to make the OCC kernel work more efficiently), and tracks operations that can only be executed after fragmentation (GetInterface and Hollow). While simple, down the line I expect this compiler to prove useful for simplifying the interface to the OCC kernel and perform more optimizations.
  • SolidModelTarget. A SourceStack + a set of operations, providing the complete recipe for turning a schematic into a solid model. Fed directly to the renderer via render!(solid_model, schematic, target). I greedily used the same name for the type that we use in the existing pipeline, as it's encapsulated in the module and not exported, but open to changing it.

Under the hood, the thing that makes it possible to create and track selections while offering a simple layer-based interface is a deduplication pass that ensures that every entity in the model belongs to one and only one PG, as this is a critical requirement by Palace and the MFEM mesh data structures. The pass partitions the model into as many PGs as needed while keeping track of their layer memberships.

Changes to existing code

In addition to this new functionality, this PR makes a few minor (and non-breaking) modifications to existing code:

  • src/schematics/components/components.jl — geometry(comp) can now access a task-local context, providing a cache and an error-recovery callback so that rendering with strict=:no can continue past a failing component. Fixes #287.
  • src/schematics/solidmodels.jl — render! now runs inside that context and a try/finally around the logfile
  • src/schematics/schematics.jl — close_logfile tolerates an already-closed stream so cleanup doesn't mask an earlier failure.
  • src/solidmodels/render.jl — _render_orchestrator! has a new keyword preflattened=false; the new
    pipeline flattens the coordinate system once itself (after qualifying locator names by graph node) and passes the result through.
  • Project.toml — adds JSON and JSONSchema as test-only dependencies (schema validation of the
    returned metadata).
  • docs/make.jl registers the module and a concept page.

Testing

  • test/test_solidmodels_experimental.jl: compiler unit tests per operation (lowering,
    lifetime absorption, rejection cases), Gmsh-backed integration tests with measured
    areas/volumes for each operation family including hollowing and interfaces,
    locator/port/tag resolution, deduplication invariants (dims 1–3), registry/model
    consistency, strictness modes, and a schematic end-to-end test that validates the metadata
    against the schema.

Limitations / follow-ups

  • Composite-component locator names are qualified one graph-node level deep; arbitrary-depth
    paths are not yet encoded.
  • Unitless coordinate systems cannot extrude nonzero distances (pre-existing ustrip(STP_UNIT, ::Float64) limitation in extrude_z!).

Miscellaneous

  • Added an AGENTS.md with a few style conventions. Feel free to reject it or modify it, although it'd probably be useful to have such a file for the project anyway, even if we end up using a different version.

TODO

  • Fix documentation. Right now it is all AI generated and horrible, but I'll wait to make it nice once we're done with the core review.

P.S. sorry for the messy commit history... I took a meandering path to get here. Maybe we just squash when we merge?

@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

@parrangoiz
parrangoiz requested a review from gpeairs September 21, 2026 16:06
Keep layer-based deduplication and connected-component splitting as separate passes. Rehash CC subgroups from final contents, update registry and split bookkeeping directly, and assert disjoint terminal and ground PGs.
Make SourceStack the sole owner of source layers and level z coordinates. Remove StackLevels, route rendering and serialization through the stack, and expose the simplified layer_z, thickness, and sourcelayer APIs.
Let Julia infer layer and level dictionary types independently while retaining unitful/unitless validation. Remove manual layer reconstruction and coordinate conversions, and update SolidModelTarget for the relaxed stack parameters.
Keep operation validation in compile_layer_ops and inline the remaining placed-layer checks at the render call site.
Traverse placed metadata once, then pass the collected records into registry construction for validation and registration.
Document the project bang-name rule and rename experimental helpers that mutate the solid model or schematic logging state.
Replace the flat deferred-interface list and separate provenance dictionary with a MetaDiGraph of PG and interface vertices. Cache Gmsh membership and boundary queries per PG, derive Tag and metadata relationships from the graph, reject duplicate destinations, simplify retention, and expand interface execution coverage.
Have Tag and terminal discovery return the metadata needed by later passes, including registered metal connected components and their entity tags. Store interface layer provenance on the deferred graph and derive it during serialization, keep deduplication and CC splitting outside serialization, simplify internal helper names, expand coverage and render-flow comments, and document commit-message guidance.
Rename the layer registry alias, consolidate compiler and interface helpers, and extract locator discovery into locators.jl. Remove obsolete registry and artwork files, colocate schematic rendering and finalization code, simplify locator coordinates and entity matching, add unmatched-locator diagnostics, update tests, and document private-helper naming.
Load SolidModelsExperimental as a top-level DeviceLayout module after its SolidModels and schematic dependencies, and fold the former schematic-integration file into the module entrypoint. Update module references and filenames, narrow the exported API, replace write_metadata with direct JSON output, and refresh tests, documentation, and examples.
Replace the specialized working-schematic reconstruction with a direct deepcopy and consistently name the disposable value sch_copy. Preserve the build and placement-prefix isolation needed for deterministic, non-mutating renders, and update the placement-copy regression coverage.
Add renderer-scoped component geometry caching and recovery so schematic solid-model renders can log failed components, substitute empty geometry, and apply strictness per invocation. Preserve experimental placement prefixing on a private schematic copy, flatten once before rendering, share strictness checks, and make logfile cleanup tolerate already-closed streams while leaving direct low-level renders unchanged.
Build a reverse entity-to-physical-group index for terminal serialization. Cache locator bounding boxes and use per-layer and terminal RTrees to reduce repeated Gmsh containment queries while preserving exact isInside validation.
Avoid redundant reference deep copies and process flattened entity-metadata occurrences directly during schematic rendering. Keep locator metadata intact, enforce unique lumped-port physical-group identities, and remove wording-sensitive exception assertions from the experimental tests.
Process flattened metadata occurrences directly, enforce unique lumped-port identities, simplify locator records, validate model PG registration before terminal discovery, and streamline metadata mapping and tests.
Use concise metadata names throughout the experimental pipeline, centralize physical-group content hashing, and keep operation destinations outside the hash helpers. Preserve schema field names and validate the refactored compiler and rendering paths.
Rename metadata and operation naming helpers, separate hash generation from caller-owned prefixes, and quiet Gmsh across the experimental solid-model tests.
Expose temporary registry and operation printers for compiler debugging, exercise interior-solid flushing through compile_ops, and derive expected PG names from compiler outputs rather than hardcoded naming details.
Make repeated translations execute with unique destination PGs, adopt PG-level copy defaults, and honor copy=false across destination layers. Centralize source-layer validation, standardize added error messages to lowercase, and update compiler coverage.
Replace tuple-based layer operations with immutable operation types and multiple-dispatch compilation. Add grouped boolean operands, typed options, concise constructors, operation documentation, and migrate experimental tests and examples while retaining PG-level tuple output internally.
Simplify compiler state names and access, compile typed operations directly through dispatch, restore operand-level operation hashing, add single-tool Difference syntax, and centralize locator-role checks.
Move optional registry and operation inspection utilities out of the production experimental API and into the compiler test item, where they remain available for debugging.
Standardize public docstrings, document exported role and operation semantics, and clarify metadata, stack, rendering, and source-lifetime behavior. Convert documentation for internal compiler and finalization helpers to comments and correct the conceptual Fuse and Heal guidance.
Move artwork rendering into a dedicated source file, colocate extrusion and exterior-boundary helpers with the compiler, and remove the redundant eager metadata traversal while retaining lazy source-layer validation.
Add OCC-backed Intersect with cross-product PG semantics, explicit-removal lowering, collision rejection, and runtime pruning of unrealized records. Rename operation commands to verbs, remove experimental exports, and update documentation, examples, and tests to use explicit imports.
Exclude source-removal policy from Cut output hashes, reject repeated generated identities, and preserve stateful and explicit composition behavior with focused coverage. Standardize typed-operation error prefixes and operation hash keywords, and document current repeated-operation semantics.
Append one disjoint fused PG when the destination exists outside the source set, while retaining replacement semantics when the destination is an explicit source. Reject repeated generated identities and expand coverage for append, repeat, stateful, chain, and dimension behavior.
Add focused compiler tests for independent collision rejection, stateful in-place reapplication, absorbed-removal collisions, and explicit prefix-preserving Heal chains.
Treat aliased object or tool layers as replaced OCC inputs, reject self-intersections, and preserve explicit removal of the resulting destination. Expand compiler coverage and document the stateful repetition behavior.
Reject repeated and aliased GetInterface destinations, preserve explicit deferred chains, and validate that every deferred input PG identity and dimension remains registered through execution. Update documentation and compiler coverage for the stricter contract.
Reject appending boundary selections to existing unrelated layers while preserving new destinations, in-place dimensional composition, explicit chains, and multi-PG sources. Remove unsafe geometric precedence subtraction and document caller responsibility for disjoint source boundaries.
Reject GetBoundary appends to existing unrelated layers while preserving in-place and chained extraction. Add context-sensitive Translate copy defaults, an in-place shorthand, collision rejection, and explicit copy/move validation with expanded repetition and composition coverage.
Add context-sensitive Translate copy defaults and an in-place shorthand, reject ambiguous out-of-place moves and generated collisions, and add one-layer Revolve construction with repeat and chain coverage. Simplify unary lowering now that all generated collisions are errors.
Reject self-pairing SetPeriodic commands at construction and document the distinct-layer requirement with focused compiler coverage.
Exercise compiled layer operations through the experimental renderer on compact analytical fixtures. Verify realized physical-group cardinality, measures, bounds, source lifetime, deferred interfaces, clipping, and periodic relationships while retaining exhaustive unit-level compiler checks.
Move registry/model consistency and deferred-interface edge cases into the public-render compiler integration suite. Cover duplicate, self, and mixed-dimensional interfaces with realized geometry measures and bounds while removing redundant standalone test items.
Move lumped-port validation and renderer side-effect checks into focused test items, rely on the end-to-end render for schema validation, and remove redundant metadata serialization assertions and the static metadata fixture.
Exercise unitless rendering through the sparse intersection compiler integration fixture and keep renderer warning policy in a focused Strictness modes test. Remove the former ad hoc unitless metadata and file-output assertions.
Physical groups are now opaque geometric partitions identified only by
name, dimension, and layer membership. User-facing identity is applied
after geometry construction by point locators rather than carried on the
geometry itself.

- Replace `EntityMeta`/`Role` (entitymeta.jl) with `LayerRef` and the
  `LocatorMeta` hierarchy `Terminal`, `Ground`, `Tag`, `LumpedPort`,
  `WavePort` (metadata.jl). Locators are placed as `Locator` point
  entities and render to no geometry.
- Resolve locators in two stages: `resolve_locators` reads positions
  and port directions from the flat geometry; after rendering, `select!`
  produces `Selection`s (entity tags plus the locators that chose them)
  for tag and port surfaces and for metal connected components.
- Rewrite deduplication to partition entities by exact PG membership so
  that every entity belongs to exactly one PG, as Palace requires. This
  single pass also isolates locator selections and connected components,
  replacing the layer-signature dedup, the CC split pass, and the
  temporary selection layers. It runs for all dimensions.
- Remove `EntityMeta` from `PGRecord`; source PGs are named after their
  layer. Interface, boundary, and cut operations no longer promise to
  preserve or collapse source identities.
- Serialize `tagged`, `ports`, `terminals`, and `ground` sections from
  selections; drop `physical_groups[*].entity_meta` and add `ports` to
  the schema.
- Prefix locator names with the graph node id on a private copy of the
  schematic during `render!`.
- Add variable-naming rules to AGENTS.md; update docs, example, and tests.
The visualization-only PG remap produced a non-Palace-compatible model in
which entities could belong to several PGs. Drop it and its end-to-end
test tail; visualization will be addressed separately.
The z-range heuristic for overlapping 3D source layers was advisory only
and had no test coverage. Overlapping volumes surface as OCC fragmentation
failures regardless.
Merge _prune_unrealized_pgs! and _check_pgs_registered into sync_registry!,
which prunes unrealized records and errors on unregistered model PGs in one
pass. Move it, retained_physical_groups, and deduplicate_pgs! to compiler.jl
alongside the registry types they operate on, dropping their underscores.
Remove the hasgroup/haskey guards in locators.jl and serialization.jl that
the synced registry makes redundant.
execute_deferred_interfaces! becomes realize_interfaces!, pairing with
defer_interface! and the registry's realized/unrealized vocabulary.
_prefix_placement_names! becomes _qualify_locator_names!, since it
qualifies component-local locator names with their graph node id; drop
the redundant LayerRef and Ground methods of its helper.
Extrusions are no longer prepended from SourceLayer.thickness; the
operation list is the complete construction sequence.

- Extrude(source), Extrude(source, dz), Extrude(source; to_level, offset),
  and Extrude(destination, source, ...) mirror Revolve, accept 1D and 2D
  sources, and consume the source in place. Source-stack layers build to
  their declared thickness or level span; explicit distances must agree
  with the declaration, and check_declared_thicknesses fails rendering
  if a declared thickness is never extruded. Generated layers require dz
  or to_level; to_level is resolved from the source geometry when the
  operation executes and recorded on the destination LayerState.
- Hollow(layer) replaces a 3D layer by its shell and, after fragmentation,
  removes the enclosed volumes and any face left bounding no volume.
  It runs before interfaces are realized so voided interiors do not
  contribute to volume boundaries. Replaces the deferred interior-solid
  subtraction (_flush_interior_solids!, intsol) and EXTBND_MISC.
- SourceLayer drops contour_only and keep_interior and renames height
  to offset; shells are GetBoundary(:x, :x); Extrude(:x).
- LayerState gains dz; metadata reports it as thickness for generated
  layers, and the schema key height becomes offset.
- Out-of-place Heal names its result by content hash instead of
  requiring a layer-prefixed source PG name.
Source physical groups are keyed by layer, so until rendering every
layer holds exactly one PG named after it. The compiler now assumes this
instead of fanning operations out over per-identity PGs.

- LayerState.pgs is a Vector{String}; PGRecord is removed. Each
  _compile! lowers one input PG to one output PG named after the
  destination via _pg and _create!. A destination must be new or one of
  the operation's own inputs; append modes, content-addressed names
  (ophash), and their collision checks are gone.
- Heal is removed: it lowered identically to a one-source Fuse, whose
  one-layer form now documents in-place healing. Translate loses its
  copy field, since in-place moves and a distinct destination copies.
- Deferred interfaces are a Vector{DeferredInterface} instead of a
  MetaDiGraph; realization runs in compile order, and the experimental
  module no longer imports Graphs or MetaGraphs.
- File-private types are underscored: _CompilerState, _HOLLOWED.
- Compiler unit tests are rewritten for the single-PG semantics, and
  repeated-operation-semantics.md reduces to the destination rule.
Julia 1.10 and 1.11 reject assigning a testitem-level variable whose
name was already resolved as an import, which failed CI when a local
`layer` followed calls to DeviceLayout's `layer`. Rename test variables
that shadow exported functions (`layer`, `geometry`, `graph`,
`component`, `cell`) throughout the file, and update the unused
`inspect_registry` helper for the string-only layer registry.
@parrangoiz
parrangoiz force-pushed the arrangop/new-sim-interface branch from 7071464 to 3fb234f Compare September 30, 2026 17:47

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schematic to solid model rendering does not honor strict for geometry failures

1 participant