Evaluated occurrence appearance policy (F6, #4404)¶
Image and finite-page requests keep representationPolicy: "preserve" by
default. "evaluatedOccurrence" explicitly permits the supported occurrence's
parametric Body to become evaluated tessellation. A viewer must explain this
tradeoff and show the converted products before Apply. Discard publishes nothing;
conversion and appearance belong to one mutation/history operation.
A supported occurrence is an IfcElement (not a StandardCase subclass or a
feature element) with a uniquely owned IfcProductDefinitionShape and one
Body shape wrapper holding one item whose RepresentationType permits
tessellation: MappedRepresentation, SweptSolid, AdvancedSweptSolid,
Brep, AdvancedBrep, CSG, Clipping, SolidModel, SurfaceModel,
Tessellation or SectionedSpine. Curve, point, annotation and bounding-box
Body types are refused explicitly. The wrapper may have ordinary
presentation-layer membership. A solid Body wrapper that a type's
IfcRepresentationMap also references is supported: the occurrence receives a
cloned wrapper with cloned plain layer assignments and its
ProductDefinitionShape list is rewired, while the original wrapper stays with
the map. A map of a MappedRepresentation wrapper is a chain and stays refused.
Otherwise the wrapper's Items and RepresentationType change in place. Its
identity, context, layer membership, product placement, product identity,
properties and semantic relationships stay intact. Non-Body curve and
bounding-box wrappers remain untouched. Shared mapped items, representation
maps, type products, source styles and sibling occurrences are never edited.
Retaining the occurrence wrapper avoids creating orphaned
ProductDefinitionShapes or accidentally sharing shape wrappers contrary to
IfcShapeModel.WR11. The first merged slice accepted mapped occurrences only;
the same funnel, exact corner check and refusals now cover the other types.
The canonical element-production funnel evaluates the original occurrence.
The first slice requires one unambiguous mesh and one explicit source surface
style. New tessellation coordinates use the inverse rigid product placement,
restoring mesh origin and RTC once, in source length units. Canonical reopening
must reproduce every oriented triangle corner exactly and retain the original
colour/material before the requested appearance is applied. A bounded single
IfcPresentationStyleAssignment wrapper can be flattened while keeping its
surface-style definition. The native image/page planners consume this private
typed source overlay and return one composite mutation plan, with original-item
provenance in conversions. Edits targeting private newly created rows are folded
into their creation records before returning the plan.
Unsupported cases remain diagnostics: aggregate-propagated voids and openings another host consumes, StandardCase subclasses, shared Body/PDS wrappers, shape aspects, styled presentation layers, additional renderable representations, multiple meshes or ambiguous/inherited styles, mapped-chain and nested geometry style overrides (even with matching colour), material-layer slicing, existing textures, and ExistingUv conversion. No alternate visible Body is added. These are first-slice limits, not claims that IFC cannot represent the cases.
Opening conversion is deliberately not enabled by this policy. buildingSMART distinguishes subtractive opening Body geometry from Reference geometry accompanying an already-cut host. Our geometry path must honor that distinction before baked opening surfaces can be published while retaining the opening relationships. See IfcOpeningElement semantics.
The real IFC4 fixture is AC20-FZK-Haus from the fixture manifest, SHA-256
ea6f04eaf92fac4d7ad0038bc3d2dfea4c094dd3f516ecc33c50bf1835ca108d.
Mapped IfcMember 35169 is one of 42 members sharing a type; its Body wrapper is
35155 and evaluated source geometry item is 35135. Native tests compare all
unselected meshes unchanged and the selected oriented world corners exactly,
exercise finite-page composition, and refuse direct/inherited openings and shared
wrappers. Independent IfcOpenShell checks compare new violations against the
source's existing validation findings; the original model is not schema-clean.
Composite output uses ascending, contiguous created IDs from nextExpressId.
Private conversions rejected by the final appearance pass leave no allocator
gaps. Generated references, replacement-item provenance and page image bindings
are rebound together before the plan leaves Rust; host allocation checks remain
strict.
Page material names equal to reserved wire tokens after trimming (#123,
.ENUM., $, or *) are refused explicitly. The current authoring wire cannot
distinguish those literals from references/enumerations/null/derived values.
Other labels, including #material and .surface, remain literal strings.
For occurrence materialization, each conversion also carries the original canonical
sourcePositions, sourceNormals, sourceOrigin, sourceColor, and rtcOffset.
sourceIndices indexes these positions. Values use IFC Z-up metres: add the f64
source origin and the f64 RTC offset exactly once around local f32 vertices.
These are moved from the same bounded evaluated mesh already checked against the
replacement, not reconstructed from the renderer's rounded instance matrices.
The existing aggregate plan geometry budget also bounds these retained arrays.
The host must resolve the model index and registered placement frame explicitly;
this payload does not make renderer instance expansion canonical UV provenance.
Face masks¶
A face mask restricts one occurrence's appearance to a reviewed subset of its
evaluated surface. Masks exist only under representationPolicy:
"evaluatedOccurrence"; a request that carries faceMasks with the preserve
policy is refused as a whole. Each accepted conversion reports a
surfaceFingerprint: the hex SHA-256 of the product GlobalId, the authored
product-local coordinates quantised to one micrometre, and the triangle
topology. Express ids are deliberately excluded, so a renumbered export keeps
a mask, while any change to the evaluated surface (an edited opening, a
different profile, another tessellator) changes the fingerprint. The authored
coordinates are rebuilt from a precision-preserving canonical local-frame
evaluation on every target, so a pure placement edit keeps the fingerprint. A
mask is { productId, surfaceFingerprint, triangles } with
source triangle ordinals in the same order as sourceIndices.
The planner never reuses triangle ordinals by position. Faults in one mask
exclude only that product, in exclusions, and the rest of the scope still
plans: a fingerprint that differs from the surface about to be authored is
Face selection is stale: the evaluated surface geometry changed; an empty
mask, an ordinal beyond the surface, and a mask on a product that already has
a direct tessellated Body each have their own reason. Faults in the request
shape refuse the whole plan as an error before any source work: masks under
the preserve policy, a mask outside the scope, two masks for one product, more
masks than products, a fingerprint that is not a hex SHA-256 digest, or more
than 500 000 triangle ordinals across all masks (the plan geometry budget's
triangle capacity, checked before any ordinal is sorted). A mask covering every triangle is an
ordinary whole-surface conversion and reports no split.
A partial mask authors one shared IfcCartesianPointList3D and two
IfcTriangulatedFaceSet items under the same Body wrapper: the masked face set
receives the image or page atlas, the retained face set keeps the complementary
triangles and a new IfcStyledItem pointing at the original source surface
style entity. Both keep the source colour and material name through the
canonical funnel, and each must reproduce its share of the source corners
exactly before the plan leaves Rust. The conversion reports maskedTriangles
(ascending) and retainedGeometryItemId; sourceIndices remains the complete
source surface so preview and history keep one original. Face masks do not
apply to direct tessellated bodies, which would require replacing a file-owned
item; the planner reports such a mask as its own exclusion.
Face masks in the viewer¶
The appearance workspace holds one face selection per converted product as
session state only: { productId, surfaceFingerprint, triangles }, bound to
the fingerprint the last plan reported for that product. Selections never
persist to IFC (the output carries the resulting face sets), clear when the
target model changes or reloads, and are spent by Apply (the product then owns
a direct tessellated Body, which cannot carry a mask). A plan request carries
only the masks of products inside its scope, and only under the
evaluatedOccurrence policy: while Convert supported objects to mesh is
off, the planner would refuse the whole request for carrying masks, so the
selections stay dormant in the session and return with the policy. Masks of
other products wait in the workspace.
Each converted object in the panel shows a row IFC object #N (the id is never
truncated), a chip (all N faces or k of N faces selected) and a
Select faces editor over the product's evaluated source surface, the same
sourcePositions/sourceIndices the plan reports, so editor triangle ordinals
are mask ordinals. In the editor Pick faces is the sticky selection mode: a
click toggles one face, a marquee adds the faces whose centres it covers (Alt
removes); All faces clears the selection, in the editor and on the row. A
selection covering every face is no selection. Every change re-plans, and the
editor keeps its renderer and camera across selection changes and re-plans that
reproduce the same surface (same fingerprint, same placed corners); only a
changed surface rebuilds it.
Pick in model keeps the main viewport in a sticky face-pick mode. Each click must resolve the open product's federation model, one of its source/textured/ retained representation items, and a canonical evaluated-surface triangle; a miss, another object, or absent/ambiguous provenance changes no selection and shows a diagnostic. Hidden and isolated objects and section/crop-clipped faces follow the same visibility rules as the rendered frame. Masked preview parts retain full-surface corner ordinals, so clicking the retained half after a split does not restart numbering at zero.
The viewer never decides staleness itself. After every plan it reconciles: a
mask whose product the planner excluded as Face selection is stale (an
edited opening, a different profile, a different tessellator) or as already
carrying a direct Body is dropped, and the panel shows `Face selection for