The codec and kernel repositories and crates are renamed opencadcodec and opencadkernel. Inside OCS they are depended on under short keys, so code reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node graph engine). File-format and protocol spellings stay as they were: the ACIS product id `acadrust`, the ACADRUST_* switches and the plugin version keys `acadrust_version` / `acadrust_source`. A button left of MCP on the command line opens a translucent node graph over the viewport, with a palette of object, input, math, logic, list, text and geometry nodes added by click or drag. An object node owns drawing entities and its rows are the Properties panel's own rows for them: the single-object page is extracted from refresh_properties as entity_property_sections, values use the control protocol's property_json, and writes go through set_property_value with a property target override, so the panel, MCP and the graph share one read/write path. A list reaching an object row makes one entity per element. Each graph action is one undo step; the canvas pans and zooms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.5 KiB
Entity tessellation paths
How each opencadcodec EntityType becomes drawable geometry in OpenCADStudio.
There are three ways an entity gets there, and they must not be conflated:
- Kernel B-rep mesh — the ACIS document is lifted into a opencadkernel
Bodyand each face triangulated in its own surface's parameter space. The only path that produces filled mesh triangles from analytic surfaces. - Curve-sampled — the entity's geometry is read through
entities::curve::entity_curve, which is where each type's curve is defined once, and tessellated to a chord tolerance. Output is a polyline; no surface is involved. - Direct — points, segments or triangles emitted straight from the entity's own fields.
The distinction between (2) and (3) is worth keeping because (2) is a single definition shared with everything else that needs an entity's geometry — snap candidates, EXTRUDE and REVOLVE profiles, hatch boundaries, clip outlines. A circle drawn on screen and a circle handed to the Model tab come from the same place and cannot disagree.
Entry points: src/scene/convert/tess.rs (tessellate_entity, the per-entity
dispatcher) → src/scene/convert/tessellate.rs (tessellate, the
RenderObject router), src/entities/curve.rs (curve definitions and
sampling) and src/scene/convert/curve_tol.rs (the per-frame chord tolerance).
Summary
| Path | Entities |
|---|---|
| Kernel B-rep mesh (lift → per-face triangulation, with a direct per-surface fallback) | Solid3D, Region, Body, Surface |
Curve-sampled (entity_curve → chord-tolerance tessellation) |
Line, Arc, Circle, Ellipse, Spline, LwPolyline, Polyline, Polyline2D, Polyline3D — non-thick variants only |
| Direct (segments/triangles emitted straight) | everything else |
The Model/Design-tab primitives (BOX, SPHERE, CYLINDER, CONE, WEDGE, TORUS, PYRAMID, EXTRUDE, REVOLVE) are true kernel B-reps too, cached on the Scene as
Bodyand meshed the same way — but they are created asSolid3Dplaceholders carrying that body, not as their ownEntityType. SWEEP and LOFT produce a mesh only, with no B-rep behind it.
ACIS solids dispatch through solid3d_tess::tessellate_acis, which lifts the
document into the kernel first (acis_kernel::tessellate_sat) and falls back
to a bespoke per-surface LOD sampler (tessellate_sat_lods) when the kernel
cannot express a face — so their path is kernel-with-direct-fallback. The
fallback is not silent: a mesh whose faces did not all lift is marked
incomplete, because one missing wall looks exactly like a finished solid.
Curved-surface tessellation density is radius-relative (CURVE_REL_TOL, a
fraction of the surface radius) so a cylinder's facet count matches the
circle/arc wire tessellation instead of exploding on large radii.
Full table
| Entity | Output | Path | Notes |
|---|---|---|---|
| Arc | wire | curve-sampled | non-thick: sampled from the arc's own curve; thick: direct Lines plus the swept wall |
| AttributeDefinition | wire | direct | routes through the Text/MText LFF glyph stroke pipeline |
| AttributeEntity | wire | direct | same as AttributeDefinition; values supplied per Insert |
| Block | none | n/a | block-definition sentinel; not tessellated (referenced via Insert) |
| BlockEnd | none | n/a | block-definition end marker; no output |
| Body | mesh | kernel-with-direct-fallback | 3D ACIS body lifted into a kernel Body; fallback per-surface sampler |
| Circle | wire | curve-sampled | non-thick: sampled from the circle's own curve; thick: direct Lines |
| Dimension | wire | direct | baked-block path recurses on D### sub-entities; synthesis path emits lines/arrows/LFF text |
| Ellipse | wire | curve-sampled | sampled from the ellipse's own curve |
| Face3D | both | direct | edge Lines + direct fan-triangulated fill_tris; no B-rep |
| Hatch | both | direct | boundary outline not emitted to the wire set (#131 OOM); fill rasterized on GPU |
| Insert | wire | direct | expands block children and tessellates each via its own path; XCLIP filter applied |
| Leader | wire | direct | leader path + arrowhead + landing, direct Lines |
| Line | wire | curve-sampled | non-thick: its two ends; thick: direct Lines plus the swept wall |
| LwPolyline | wire | curve-sampled | plinegen true: sampled from its own curve, bulges kept as arcs; else direct SegmentedLines |
| Mesh | both | direct | SubD mesh: edge Lines + direct fan-triangulated fill_tris |
| MLine | wire | direct | spine + offset lines + caps, direct Lines |
| MText | wire | direct | wrap-aware multi-line LFF glyph layout; inline formatting codes |
| MultiLeader | wire | direct | leader + landing + LFF text + frame + fill |
| Ole2Frame | wire | direct | bounding rectangle + diagonal cross |
| Point | wire | direct | a position → dot or cross marker sized by PDSIZE |
| PolyfaceMesh | both | direct | face list: closed-polyline edges + direct fan-triangulated fill_tris |
| PolygonMesh | both | direct | M×N grid wireframe + direct fan-triangulated fill_tris |
| Polyline | wire | curve-sampled | heavy 3D polyline; sampled from its own curve, bulges kept as arcs |
| Polyline2D | wire | curve-sampled | 2D polyline with bulge; sampled from its own curve |
| Polyline3D | wire | curve-sampled | straight edges, no bulge or thickness |
| RasterImage | wire | direct | boundary rectangle / clipping polygon |
| Ray | wire | direct | two-point [base, base + dir×1e6], no sampling |
| Region | mesh | kernel-with-direct-fallback | 2D planar ACIS body; same kernel path as Solid3D |
| Seqend | none | n/a | vertex-sequence terminator sentinel; no output |
| Shape | wire | direct | small diamond marker at the insertion point |
| Solid | wire | direct | 2D SOLID: four quad edges as direct Lines |
| Solid3D | mesh | kernel-with-direct-fallback | 3DSOLID: parse SAT/SAB → lift into a kernel Body; fallback sampler |
| Spline | wire | curve-sampled | NURBS sampled through the kernel's space curve, refined where it bends |
| Table | both | direct | cell fills (fill_tris) + LFF cell text + grid lines |
| Text | wire | direct | LFF-font stroked glyph polylines |
| Tolerance | wire | direct | feature-control frame grid + per-cell LFF symbols |
| Underlay | wire | direct | boundary rectangle of the PDF/DWF reference |
| Unknown | none | n/a | unrecognized-entity sentinel; no output |
| Viewport | wire | direct | content-viewport frame rectangle (sheet viewport skipped) |
| Wipeout | wire | direct | boundary rectangle / clipping polygon |
| XLine | wire | direct | three-point infinite line, no sampling |