Changelog
The canonical changelog lives at CHANGELOG.md in the repository root and follows Keep a Changelog. This page is synchronised from it at build time.
Unreleased
Added
ifc-alignment: HELMERTCURVE and SINECURVE horizontal transition segments lower exactly asCurve2::Intrinsic. Helmert is one curve carrying aCurvatureLaw::Piecewisewith a seam at half length and two quadratic pieces, each written in its own rebased arc length; splitting it into two curves is impossible without the seam position, a non-elementary integral. Sine uses the kernel'ssine_corrected_transition, matching IFC's published law exactly. VIENNESEBEND is now the only refused horizontal family: its law needs the cant swing from the separateIfcAlignmentCantlayout. Still no quadrature, series, or sampling in the crate.ifc-validate: WHERE rules for the relationship familiesifc-spatialreads.IfcRelSpaceBoundary.CorrectPhysOrVirtties declared physicality to the bounding element across all three concrete subtypes,NoSelfReferencecovers the fourIfcRelAssignssubtypes, each naming its own relating attribute, andIfcRelConnectsPathElementspriorities are bounded to 0..=100 with an empty list treated as conformant per the rule's own OR clause.ifc-alignment: CLOTHOID, BLOSSCURVE and COSINECURVE transition spirals lower exactly asCurve2::Intrinsic, the natural-equation curve added in axiolid-curve v0.12.0. A spiral has no elementary parametric form, but its curvature is an elementary function of arc length, and a plane curve is fixed up to rigid motion by that law; anchoring it to a start frame fixes it absolutely. Storing the law is therefore lossless -- the crate still performs no quadrature, series expansion, or sampling anywhere. HELMERTCURVE, SINECURVE and VIENNESEBEND remain a typed refusal: their laws need terms anIfcAlignmentHorizontalSegmentdoes not carry, and forcing them into a nearby law would be a silent approximation.ifc-alignment: composite runs now end after a transition spiral rather than asserting a transition across it. Continuity into a spiral is provable from the predecessor's closed-form end point, but continuity out of one is a Fresnel-type integral, andTransitionhas no "unknown" member -- so the run ends instead of claiming a fact the crate cannot verify.ifc-geometry: theIfcSameValuetolerance family (IfcSameValue,IfcSameCartesianPoint,IfcSameDirection,IfcSameAxis2Placement), plusIfcPointListDim,IfcOrthogonalComplementandIfcBuild2Axes, transcribed inresource::functions. The schema's comparison band is exclusive -- values exactlyepsilonapart are not equal -- so a symmetricabs() <= epswould have been subtly wrong; a unit test pins the boundary.IfcSameAxis2Placementdiverges deliberately: the published function comparesap1.Locationwith itself, never testing the location at all, and that defect is not reproduced.ifc-spatial: space boundaries.relation::boundaryreadsIfcRelSpaceBoundaryand both concrete subtypes, resolving the bounded space, the bounding element,PhysicalOrVirtualBoundary,InternalOrExternalBoundary, and theParentBoundary/CorrespondingBoundarylinks that make second-level boundaries usable for heat transfer.Model::ids_of_typematches exact type names, so all three concrete types are queried: a lookup of the supertype alone misses every real BEM export and reports a building with no boundaries at all. The fourEXTERNAL_*exposure members are kept apart from plainEXTERNALrather than merged, since ground and water contact are different heat-transfer paths.CorrectPhysOrVirtagreement is reported throughphysical_matches_element, not enforced: this crate states what the file says and leaves rejection toifc-validate.ifc-spatial: coverings.IfcRelCoversBldgElementsandIfcRelCoversSpacesjoin the generic relationship reader, so a finish is reachable from the element it clads and from the space it bounds. The two stay distinct: one suspended ceiling can cover a slab and bound a room, and collapsing them loses which question was asked. Layer order is preserved, since a build-up is ordered.ifc-spatial: element connection and interference.IfcRelConnectsElementswith itsPathElementsandWithRealizingElementssubtypes, plusIfcRelInterferesElements. The connects family placesConnectionGeometryat slot 4, so its two ends sit at 5 and 6 rather than 4 and 5; reading the usual pair would name the connection geometry as the relating element. Interference is not a subtype of it and keeps the 4/5 layout. Both positions are asserted against the shipped schemas. Connection and interference stay separate kinds: a clash is not an adjacency.ifc-spatial: assignment relationships.IfcRelAssignsToActor,ToProcess,ToProductandToGroupByFactorjoin the generic reader, answering who is responsible for an object, which task consumes it, and what it belongs to. Their two ends bracketRelatedObjectsType: the related list comes first at slot 4, the enumeration sits at 5, and the relating end is at 6. A reader assuming the usual "relating at 5" finds an enumeration rather than a reference and the assignment disappears with no error, so the positions are asserted against the schemas.ifc-spatial: the last five relationship families.IfcRelDeclares(what a project context declares),IfcRelDefinesByObject(an occurrence defined by another),IfcRelFlowControlElements(controls governing a flow element),IfcRelServicesBuildings(which structures a system serves) andIfcRelConnectsWithEccentricity. These five do not share a layout: three put the relating end first, two put the related list first, and inverting either direction is silent. With these, all 42 concreteIfcRel*families in IFC4 are read.
Changed
ifc-alignment: kernel pin moved from axiolid v0.12.0 to v0.14.0, forCurvatureLaw::PiecewiseandCurvatureLaw::Composite.ifc-geometry: the EXPRESS function registry now recordsImplementedandNotApplicablealongsideScaffolded, and no row remainsScaffolded: 20 implemented, 6 native primitives, andIfcListToArray/IfcMakeArrayOfArraymarkedNotApplicablebecause they only re-index a LIST into an ARRAY, which aVecalready is.ifc-geometry: five functions were implemented but filed under the wrong owner or leftScaffolded--IfcCurveDim(rules::dimension),IfcBaseAxis(resource::axes), andIfcBuildAxes,IfcFirstProjAxis,IfcSecondProjAxis(alltransform).
Fixed
CI now runs the schema-backed tests.
references/ifc-specis not committed (CC BY-ND 4.0), so every test loading it silently skipped -- including in CI, which is how an IFC2X3 slot-name bug reached main green.scripts/fetch-ifc-schemas.shfetches the three normative schemas against pinned checksums of line-ending-normalised content, andIFC_SPEC_REQUIRED=1turns a skip into a failure.ifc-spatial:tests/slot_layout.rsasserted the IFC4 attribute name against every bundled schema. IFC2X3 spellsIfcRelCoversSpacesslot 4RelatedSpace; IFC4 renamed it toRelatingSpace. The slot position is 4 in both, so the reader was always correct -- only the test was wrong, and it failed on a schema the crate supports. The expected name now resolves per version, leaving the IFC4/IFC4X3 assertions intact.ifc-geometry: the WHERE-rule coverage gate matched violations by rule label alone, so a violation raised by any entity satisfied any other entity's case for the same label.IfcBooleanResult.SameDimpassed on a violation raised byIfcPolyline.SameDim. The gate now matches the entity type as well, accepting a declared subtype or a dimensionality-suffixed concrete form (IfcCartesianTransformationOperator3Dfor the operator supertype) and nothing wider. The equivalent hole in the naming gate had already been closed; the behavioural gate had kept it.ifc-geometry:IfcBooleanResult.SameDimhad two implementations. The older one resolved operands through a localoperand_dimthat answeredSome(3)for every family it recognised andNoneotherwise, so its comparison was unreachable -- dead code that still read as enforcement. Removed it along withoperand_dim;boolean_operandsowns the rule and resolves dimensionality throughdimension::dim_of.ifc-geometry:resource::functionsrecorded eight functions asScaffolded("semantics remain to implement") that were already executing. AddedFunctionStatus::Implementedand a manifest test asserting anImplementedrow is named by its owner module, so the registry can no longer understate the crate.ifc-geometry: aScaffoldedrow in the EXPRESS function registry can no longer stay stale after its function is implemented. The manifest checked only thatImplementedrows were backed by code, so the registry rotted downward instead: eight rows understated the crate for two commits. The inverse check is now enforced.docs: the "Objectified relationship traversal" capability row saidifc-spatial::relationreads threeIfcRel*families and that other families "are not interpreted". Sixteen crates read 26 of the schema.s 40 concrete families: assignments, definitions, connections, port connectivity, voiding and filling, sequencing, and associations. The row now lists them by crate. It sits outside the generated sentinel blocks, so nothing had checked it against the source; a newopenbim-ifc/tests/relationship_census.rsre-counts the families and fails when the stated number drifts from what the crates actually read.docs: the relationship census counted doc-comment mentions as readers, overstating it as 26 across 16 crates. A module doc that names a family to contrast its slot layout with one the crate does read is not a reader:IfcRelServicesBuildingsandIfcRelSpaceBoundarywere counted this way while no crate reads either. The real figure is 24 across 14 crates, with 16 families unread.relationship_census.rsnow skips comment lines.docs: two capability rows carried a literal...[truncated]marker on the published site, one since 3e349cf. Both restored to full prose.
Added
ifc-alignment:lower_horizontal_layout_partiallowers a horizontal layout as far as exactness allows instead of failing the whole layout on the first transition spiral. Real railway and highway alignments interleave spirals between their lines and arcs, so the all-or-nothing entry point refused essentially every production file. The partial result keeps maximal runs of exactly-lowered consecutive segments and reports each refused segment with its entity id and authoredPredefinedType, so a caller can say "3 of 5 lowered, CLOTHOID #103 and #107 refused" rather than only that something failed. A run ends at every refusal: continuity across a segment this crate did not lower is not a fact it is entitled to assert. Still no approximation anywhere --lower_horizontal_layoutkeeps its exact all-or-nothing contract unchanged.ifc-alignment: repinned the neutral kernel toaxiolidv0.12.0, whoseCurve2::Intrinsiccarries a curvature-law-plus-start-frame natural equation. This is the representation the transition-spiral families need to be storable exactly; lowering them onto it is the follow-up to this change, not part of it.ifc-geometry: the remaining 20 IFC4 geometry-resourceWHERErules are enforced, completing all 95. Two readers were added under their declared owners rather than inside the rules layer:surface::basisimplementsIfcGetBasisSurface/IfcAssociatedSurface, andsolid::brep::non_advanced_faceswalks shell faces. These feedSameSurface,DistinctSurfaces,HasAdvancedFacesandVoidsHaveAdvancedFaces; the rest cover boolean operands, composite-curve continuity, directrix bounding, trim value kinds, the revolution axis, and mapped representations.ifc-geometry:FunctionStatus::Implementedrecords that a normative EXPRESS function is actually executed, and a manifest test rejects any row claiming it whose owner module does not name the function. Eight rows that understated the crate were corrected.Enforced the schema's normative EXPRESS functions, taking executable
WHERErules from 63 to 75 of 95.src/rules/express.rstranscribesIfcConstraintsParamBSplineandIfcConsecutiveSegmentsand is unit-tested against the specification text itself, so the transcription is checkable line by line rather than only through the rules that call it. Covers B-spline curve and surface parametrisation, weight positivity, knot/ multiplicity correspondence, indexed poly-curve continuity, tapered profile correspondence, andIfcLocalPlacement.WR21.IfcLocalPlacement.WR21honours the function's three-valued result: the schema returns UNKNOWN for grid placements and unrecognised shapes, which EXPRESS treats as satisfied, so only the single explicit FALSE branch -- a 3D relative placement on a 2D parent -- is reported.ifc-georef:GeorefView::for_modelpins the declared schema to IFC4 or IFC4X3; IFC2X3 is refused withGeorefError::UnsupportedSchemasince it declares no georeferencing entities at all (verified againstIFC2X3_TC1.exp).resolve_project_to_map_inresolves anIfcMapConversionthrough a pinned view, distinguishing an IFC4X3-only coordinate-operation entity (IfcMapConversionScaled,IfcRigidOperation) read under IFC4 -- a schema mismatch -- from a genuinely wrong entity id.ifc-georef:compose_project_framechains a resolved project-to-map operation onto a separately supplied project frame (IfcLocalPlacementor equivalent, owned byifc-geometry), producing one map-frame transform; refuses a singular (non-invertible) project frame rather than propagating a degenerate composed transform.ifc-georef:NorthReference::{Project,True,Grid}distinguish IFC's three north references.resolve_true_northreadsIfcGeometricRepresentationContext.TrueNorthwhen declared and falls back to IFC's documented default (the project Y axis) when absent, rather than silently treating "unspecified" as "equal to grid north".grid_north_directionderives grid north from the resolved map conversion's own rotation, so true, grid, and project north can all disagree simultaneously, matching real georeferenced files.ifc-alignment:AlignmentView::for_modelpins the declared schema to IFC4X3 (IFC4X3/IFC4X3_ADD2); IFC2X3 and IFC4 ADD2 TC1 are refused withAlignmentError::UnsupportedSchemasinceIfcAlignment*entities do not exist in either schema at all.ifc-alignment:CantLayoutresolves, orders, and C0-continuity-checks a fullIfcAlignmentCantprofile, withcant_at/cant_at_distanceevaluating all sevenIfcAlignmentCantSegmentTypeEnumclosed-form base formulas (BLOSSCURVE, CONSTANTCANT, COSINECURVE, HELMERTCURVE, LINEARTRANSITION, SINECURVE, VIENNESEBEND) exactly -- cant states its elevation value directly as a function of arc-length, so every type is exactly representable with no integration and no approximation.ifc-alignment:lower_horizontal_layoutassembles anIfcAlignmentHorizontal's nested segment chain into one continuity-awareCurveRelation::Composite, observing (not assuming) theTransitionbetween consecutive segments from exact endpoint equality.ifc-alignment:resolve_linear_placement/resolve_point_by_distanceresolveIfcLinearPlacementthroughIfcAxis2PlacementLinearto the mandatoryIfcPointByDistanceExpression;station_equationsresolves everyIfcReferentcarryingPset_Stationinginto its distance-along/station mapping, including station-equationIncomingStationdiscontinuities.Enforced 47 more IFC4 geometry
WHERErules, taking the executable count from 16 to 63 of 95. New rule modules cover dimensionality (via a transcription of the schema's ownIfcCurveDimderivation), positive scalars, list cardinality, type membership and surface degeneracy. Every newly enforced row carries a conforming and a violating model intests/where_rule_inventory.rs, and the coverage assertion fails if a row claims implementation without one.ifc-resource:IfcPerson,IfcOrganization,IfcOrganizationRelationship,IfcPersonAndOrganization, andIfcActorRoleprojections, enforcingIdentifiablePerson,ValidSetOfNames, andWR1(USERDEFINEDroles requireUserDefinedRole).ifc-resource: all six concreteIfcConstructionResourceTypekinds, withIfcRelDefinesByTypeassignment resolution that refuses a second relation naming a different type for the same occurrence.ifc-resource:IfcInventorymetadata andIfcActorSelectjurisdiction projection, withIfcRelAssignsToGroupmembership resolved in authored order.ifc-resource:IfcPhysicalSimpleQuantity(all six concrete measure kinds) andIfcPhysicalComplexQuantityusage-quantity projections, enforcing the shared non-negative/finite value rule andNoSelfReference.ifc-resource: IFC4X3 ADD2 accepted alongside IFC4 ADD2 TC1 for every resource, actor, inventory, and usage-quantity projection (entity shapes verified identical againstIFC4X3_ADD2.exp). IFC2X3 remains an explicitUnsupportedSchemarefusal: it does not declareIfcConstructionResourceType,IfcResourceTime, orPredefinedTypeonIfcConstructionEquipmentResource/IfcCrewResource, so there is no normative behavior to project.
Fixed
IfcBooleanClippingResult.OperatorTypewas implemented under the labelFirstOperandType, so the operator check reported the wrong rule and the realFirstOperandType-- the first operand must be a swept area, swept disc or nested clipping result -- was never checked at all. Both now exist under their schema names.IfcSubedgenow lowers instead of being refused. A subedge states its ownEdgeStart/EdgeEndand inherits the carrier curve fromParentEdge, which is reached by walking the parent chain:ParentEdgeis typedIfcEdge, so a subedge of a subedge is legal and stopping at the first hop would leave the carved edge with no geometry.
Fixed
IfcGeometricRepresentationContext.WorldCoordinateSystemis now applied when lowering a product. It is a mandatory attribute defining model space, but was read and tested without ever reaching the lowered frame, so a file that surveys its site into a real coordinate system placed every product at the wrong location. Nearly all files write the identity, which is why a corpus pass never revealed it. The context frame composes above the placement chain, so a rotated context rotates the sited product.Parameter-space lowering for
IfcTrimmedCurveandIfcCompositeCurvep-curve reference curves; trim parameters stay unscaled because a (u, v) address is dimensionless.Typed
position()placement views on all four elementary surfaces.select::subtype::TABLE_SCHEMA_VERSIONnames the IFC schema the compiled supertype chains encode, withtables_are_verified_forto ask before trusting them for another version. The version was previously prose only.Membership tests for the four select families that resolved without them:
CsgSelect,CurveOrEdgeCurve,SurfaceOrFaceSurface,PointOrVertexPoint.CON-GRID:IfcGridAxisrule checking.WR1reports an axis curve that is not 2D;WR2reports an axis that is not in exactly one of a grid's U/V/W lists.WR2is stated over inverse attributes, which Part 21 files do not carry, so it is counted from the owning grid instead.RULE-REG: an executable inventory of every geometry-resourceWHERErule.data/ifc4-where-rules.tsvrecords all 95 rules the IFC4 ADD2 TC1 schema declares across 56 entities, each markedimplemented(19) orinventoried(76), with the schema's own expression text. Four tests fail the build if the inventory drifts from the schema, invents a rule, or claims an implementation therulesmodule does not provide.SEL-POSNEG: membership tests forCsgSelect,CurveOrEdgeCurve,SurfaceOrFaceSurfaceandPointOrVertexPoint, pinning the branch order that makes anIfcVertexPointa vertex rather than a plain point.
Added
docs/schema-looseness.mddocuments refusals caused by the IFC schema permitting a combination the geometry cannot honour, separating them from unimplemented features. Covers the parameter-space conic on a 3D placement and the surface curve whose master names an absent side.
Fixed
- The docs site rendered generated inline HTML as literal text: status badges appeared as
<span class="status-implemented">Implemented</span>and theCAPABILITIES:*:BEGINregion markers were visible on the page. VitePress hadmarkdown.html: false, which escapes both. The badge CSS was already present and styled, so rendering was always the intent.scripts/check-inline-html.pynow fails the gate if the setting regresses. IfcProfileDefwas labelledPlannedin the capability table, implying pending work. It is the bare supertype carrying no section geometry, so the refusal is permanent; it now readsRefusedand its doc comment no longer claims it awaits a neutral contract.
Added
- Parameter-space
IfcIndexedPolyCurvenow lowers explicitIfcArcIndexsegments. A three-point arc composes exactly from a 2D circumcentre intoCircle2plus a Cartesian trim, mirroring the 3D path with no approximation and no unit conversion. The sweep sense follows the 2D cross-product sign, so a clockwise arc is not silently replaced by its complement. - Corrected two stale typed-refusal rationales.
IfcSubedgeandIfcVertexLoopclaimed axiolid had "no neutral topology contract"; it exportsVertex,Edge,EdgeUseandLoop. Both stay refused, now for their real IFC-side reasons. IfcPolygonalBoundedHalfSpacenow lowers exactly, closing the last entry in thePLANNEDfamily table. It maps toSolidOperation::BoundedHalfSpacewithPositioncarried as the operation's own placement, independent ofBaseSurface: the authored clipping prism is neither dropped (which removes material outside it) nor folded into the clip plane (which relocates it). The axiolid pin moves tov0.11.0.IfcSurfaceCurvenow lowers whenMasterRepresentationnamesPCURVE_S2, and each associated p-curve is paired with the surface it lies on rather than kept in a flat list.PCURVE_S1/PCURVE_S2are positional slots, so with an unordered list neither could be named without guessing, and the case was refused outright. Axiolid v0.10.0 records the pairing, so the neutral master names S1 and S2 exactly. A file namingPCURVE_S2with only one p-curve names a side that does not exist and is refused as degenerate.IfcPCurvereference curves now also admit the explicit-knotIfcBSplineCurveWithKnotsandIfcRationalBSplineCurveWithKnotssubtypes. Knots are curve parameters, not lengths -- the 3D path already passes them through unscaled -- and parameter-space control points are read as raw(u, v)pairs, so no length factor applies to either. Convention-onlyIfcBSplineCurvestays refused because it carries no authored knot vector, and parameter-space trimmed and composite curves stay refused pending a trim contract. The variant catalog and generated capability page were updated in the same change; the#27runtime-checked gate caught the stale claim automatically. Evidence: a unit test, a corpus fixture case, and a mutation probe scaling the knots that the test catches.- Geometry interpretation now carries an aggregate-element budget.
SessionLimits::max_aggregate_elements(default 16,777,216) bounds the file-declared aggregates that drive allocation: B-spline knot multiplicity totals, B-spline control grids (u x v), and triangulated face-set index counts. Over-budget input produces the new typed, locatableGeometryError::AggregateTooLargenaming the entity and the aggregate; nothing is ever truncated to fit. Products are computed inu128so a huge declaration cannot wrap to a smallusizeand pass the check. - Fixed a reachable panic:
KnotVector::total_multiplicitysummed file-controlled multiplicities with an uncheckedsum(), which aborts in debug and wraps in release. It andKnotVector::expandednow returnOptionand report overflow instead. Exact output below the limits is unchanged. - Lowering capability claims are now variant-accurate. A
PARTIALcatalog inifc-geometry/src/lower/dispatch.rsrecords, per authored form, whether a family admits or refuses it and why, so families that were reported as flatly "Implemented" while still refusing some inputs (IfcPCurve,IfcSurfaceCurve,IfcIndexedPolyCurve) now report as Partial with a per-variant table indocs/capabilities.md. The catalog is not documentation:declared_variant_support_matches_runtime_behaviourdrives the real lowering path for every declared variant, so a wrong disposition -- in either direction -- fails CI. 4/4 mutation probes caught, including one that initially escaped and prompted the runtime cross-check. IfcPCurvereference curves now admit the analyticIfcLine,IfcCircleandIfcEllipsefamilies in addition to polylines. Parameter-space values are read verbatim: a p-curve conic's radius and semi-axes are displacements in the surface's own(u, v)domain, which for a cylinder mixes an angle with a length, so no length or angle factor may be applied. AnIfcLine'sDirmagnitude is preserved un-normalized because it sets the parameter scale. OnlyIfcAxis2Placement2Dis admitted for a parameter-space conic; a 3D placement is a named typed refusal, since its axis has no meaning in a parameter domain. Parameter-space B-splines, trimmed and composite curves remain named typed refusals. Evidence: 8 unit tests, a hand-authored millimetre corpus fixture (synthetic_parameter_space_conics.ifc) whose unit choice is itself the assertion, and 5/5 mutation probes caught.ifc-geometryrepins Axiolid toc144808d(v0.9.1), superseding the previous8401f908pin; no production API changes, both feature columns build and the full test suite passes at the new pin. Confirms the fix for axiolid/kernel#83 (bounded_half_spaceagreement=falsemirroring defect), independently re-verified with the original reproduction probe.IfcPointOnCurveandIfcPointOnSurfacelower toaxiolid_model::PointOnCurve/PointOnSurface, preserving the basis reference and parameter(s) exactly. Parameters convert per the basis's own quantity kind (angle on a conic, dimensionless ordinal on a polyline, length otherwise) using the same conversion the trim-parameter path already established, rather than duplicating unit logic.- The p-curve reference-curve family (
IfcPcurve's parameter-space geometry) now also accepts the implicit-orderIfcIndexedPolyCurveform -- no explicitSegments, which the schema defaults to straight lines between consecutive points -- not onlyIfcPolyline. An indexed polycurve with an explicit arc segment remains a named typed refusal: this crate carries no parameter-space arc contract, and flattening the arc to a straight line would silently change the surface parameterisation.
Fixed
ifc-geometry(data/ifc4-representation-item-dispositions.tsv) no longer listsIfcPointOnCurve/IfcPointOnSurfaceasplanned-exact; they are implemented and the disposition/IMPLEMENTEDsets must stay disjoint.ifc-schemacan export deterministic, release-identified structural catalogs for IFC2x3 TC1, IFC4 ADD2 TC1, and IFC4X3 ADD2. The export preserves exact entity names, nearest-first ancestry, and inherited Part 21 attribute order without redistributing EXPRESS sources or specification prose. A direct mode emits only each entity's immediate parent and declared slots; reconstruction tests prove it expands byte-for-byte to every release's full structural rows.ifc-geometrynow lowers authoredIfcArbitraryOpenProfileDefpaths to AxiolidOpenProfilegraph nodes without inventing area, width, or a closing segment. Area-only swept-solid callers continue to refuse them explicitly.ifc-structuralnow transaction-stages selected members, connections, point/linear/planar actions, member-connection links, and activity assignments with projected-state validation; a facade test proves one physical-productEntityIdcomposes with structural assignment and authored Body geometry while body-only plan selection remainsNone. Nine non-redundant mutants are killed.ifc-structuralnow projects IFC4+ ordered load configurations, point reactions across all supported versions, IFC4+ curve/surface reactions, and strict result-group reaction assignments; focused tests kill 10/10 semantic mutants without interpreting or computing result values.ifc-structuralnow projects typed cross-version boundary stiffness selectors, failure/slippage connection conditions, distinct varying-member identities, and IFC2X3 varying-surface thickness/location payloads without interpreting them as solver constraints; focused cross-version tests kill 8/8 semantic mutants.Bounded IFC4 approval and constraint domains now provide strict borrowed projections, typed resource/object relationships, deterministic direct queries, and transaction-staged authoring; classification now owns the generic external- reference relationship. A facade/STEP test proves all three join by
EntityId.Domain completion foundations add transaction-staged IFC4 cost-value, item, schedule, ordered nesting, and schedule-assignment authoring with typed pre-staging refusal; a reusable reverse-index-backed spatial relationship index; and a facade proof that material semantics and geometry inputs join on the same
EntityIdwithout sibling dependencies or duplicated slot ownership.ifc-resourcenow provides a bounded IFC4 ADD2 TC1 construction-resource slice: schema-resolved projections for six occurrence kinds and authored resource time, deterministic allocation lookup, budgeted authored-order composition, and transaction-staged creation of selected records and relationships. SELECT membership, enum values, inheritedRelatedObjectsTypecategory matching, finite positive ratios, aggregate bounds/uniqueness, references, model-wideGlobalIduniqueness, cycles, multiple parents, and traversal budgets are typed refusals; scheduling, levelling, calculations, actor/inventory, resource-type, IFC2X3, and IFC4X3 semantics remain outside the claim.ifc-author::EntityEditorschema-checks the full projected record before staging updates throughifc-model::Transaction; invalid names, arity, required values, types, aggregates, references, and stale commits remain typed refusals with atomic non-mutation.ifc-stepproves deterministic model-order output and bit-exact finite-real round trips, preserves signed zero, and refuses nested non-finite values with entity/slot context before serialization.ifc-xmladds an explicit strict IFC4 ADD2 TC1 namespace/profile mode and inspectableXmlPathdiagnostics through entities, attributes, typed values, and list indexes, and refuses nested non-finite REAL writes before output. Its schema-less compatibility dialect remains available without being described as generic XSD conformance.ifc-geometrylowers additional exact curve/surface families into neutral Axiolid graphs: ellipses, offset and indexed poly-curves, p-/surface curves, and curve-bounded surfaces; explicit Body/Plan selection prevents silent representation substitution and a committed IFC corpus exercises dispatch.ifc-geometrynow mechanically reconciles every concrete IFC4 representation item/profile family with an owner, exact route or typed refusal, and committed corpus behavior. It preserves composite-on-surface relations, refuses bounded half spaces whose polygonal cutter cannot yet be represented exactly, validates finite orthonormal right-handed neutral frames, and exposes geometry-only material profile/layer usage inputs.ifc-georefresolves IFC4 projected CRS metadata andIfcMapConversioninto a project-metre to map-metre neutral transform with strict unit, scale, and axis validation.ifc-alignmentresolves IFC4X3 horizontal, vertical, and cant segment parameters and emits exact neutral line, circular-arc, and constant-gradient curve graphs with schema-valid IFC4X3 corpus evidence, refusing unsupported transitions rather than approximating.ifc-template-catalogembeds authenticated official PSD/QTO snapshots for IFC2X3 TC1, IFC4 ADD2 TC1, and IFC4X3 ADD2, while keeping corrected overlays IFC4-only. It exports deterministic, edition-explicit TSV applicability indexes with exact set/member types, release-scoped source GUIDs, selectors, source paths, and digests; IFC2X3's source snapshot contains no standardized QTO XML.Public contributor workflow with a curated organization Project, contributor- ready plan-task promotion, issue forms, a pull-request template, and support, security, and conduct policies.
ifc-structuralnow provides schema-resolved IFC2X3/IFC4/IFC4X3 borrowed projections for analysis/load/result groups, analytical curve/surface members, point/curve/surface connections and actions, and four core static-load value families. Bounded assignment/connection queries validate aggregate bounds, canonicalSETuniqueness, selects, dangling references, target ancestry, group self-reference, single-attachment activity semantics, relation-record file order, and IFC2X3ProjectedOrTruerequiredness. Reads enforce canonical enum membership plus exposedUSERDEFINED/ObjectType, action-load compatibility, concrete linear/planarCONST, projected-action, required-axis, positive-thickness, and finite-measure contracts. Selected analysis-model and load authoring is transaction-staged and rejects non-finite STEP-real values before staging. No solver, FEM, geometry evaluation, reaction-value interpretation or computed-results capability is claimed.ifc-stylenow provides schema-resolved IFC2x3/IFC4/IFC4X3 views for colour/factor, curve/fill/text/surface styles, textures, presentation layers, styled items, and the requestedIfcAnnotation,IfcTextLiteral,IfcTextLiteralWithExtent, andIfcAnnotationFillAreacontracts. Core style and annotation graphs have transaction-staged authoring; direct styles resolve deterministically ahead of layer styles, and IFC2x3 wrappers remain explicit. A public 70-declaration appearance census distinguishes strict views from schema-only and structural-only support; no rendering capability is claimed.ifc-classificationimplements strict borrowed IFC4 views for classification, document, and library information/references plus their three object association families. Queries provide bounded hierarchy traversal and keep occurrence/type classifications separate; matching helpers transactionally author all nine concrete records with pre-staging validation.ifc-schemabundles IFC2x3 TC1 (653 entities, 327 types), IFC4 ADD2 TC1 (776 entities, 397 types), and IFC4X3 ADD2 (876 entities, 436 types) as independent generated artifacts.ifc2x3(),ifc4(),ifc4x3(), andfor_version()route declared schemas without cross-version fallback.ifc-validatechecksSTRING(n) FIXEDwidths from the schema, catching malformedIfcGloballyUniqueIdvalues (STRING(22) FIXED) at any length.ifc-validatecorpus tests validate all 38 committed fixtures against their declared IFC2X3, IFC4, or IFC4X3 tables: 31 are model-level clean (including two raw-header-only defects normalized before validation) and all 7 observable known-bad fixtures are rejected.cargo run -p ifc-validate --example audit_corpusprints the same audit as a triage table.ifc-validateimplements structural and type validation plus eight selected native checks exposed as nine version-labelled rule IDs: single project, unique GlobalId, property/type assignment, external-reference identity, sequence endpoint inequality, aggregate/nesting self-reference, and material priority bounds. Aggregate bounds, arbitrary EXPRESSWHEREexpressions, INVERSE derivation, geometry-dependent rules, and other known gaps remain explicitly unsupported, so a conformant verdict never claims full EXPRESS conformance.ifc-schemadelegates supertype chains and Part 21 positional attribute order toopenbim-step 0.4.0; neither is IFC-specific. Version identity, independent bundled artifacts, and process caches stay here.
Removed
- Removed the obsolete
ifc_geometry::kernelcompatibility facade and adapterToleranceplumbing. Exact lowerers no longer advertise kernel execution or approximation policy; those remain neutral-provider concerns.
Changed
- Replaced architecture ASCII sketches with accessible, dark/mobile Mermaid diagrams; IFC geometry docs now link to Axiolid's source-backed STL and equation guide. Progressive-context discovery ignores external
node_modulestrees that can contain foreign agent instructions. - Updated all user-facing Git dependency examples to the exact reviewed IFC revision that adopts Axiolid's nested package architecture.
ifc-geometrynow follows Axiolid's nested package architecture. Direct test or tool consumers must replaceaxiolid-scalar/axiolid_scalar::ScalarCompilerwithaxiolid-reference/axiolid_reference::ReferenceMeshCompilerand useMeshCompiler::{compile_mesh, compile_mesh_batch, compile_mesh_batch_into}for graph-to-mesh projection. Model-only facade consumers remain onaxiolidwithdefault-features = false, features = ["model"].
Fixed
ifc-template-catalogsource generation now canonicalizes the declared corpus root, rejects symbolic links and non-file/non-directory entries during XML traversal, and verifies every resolved candidate remains inside that root.ifc-xmlnow binds the XSI namespace used by null values, independently resolves qualified attributes during tests, and rejects unmarked or namespace-spoofed self-closing typed values instead of silently decoding them as null.Non-uniform mapped transforms now carry half-space plane normals through a scale-safe inverse transpose with scale-aware determinant orientation, preserving the authored cutting plane under ordinary and extreme finite affine scale and shear without overflow or underflow.
IfcPolygonalBoundedHalfSpaceno longer drops its positioned polygonal bound and masquerades as an unbounded half space. It now returns a typed unsupported result until the neutral geometry contract can preserve the effective cutter.ifc-geometrynow lowers schema-validIfcBoundaryCurveandIfcOuterBoundaryCurvemembers ofIfcCurveBoundedSurface, preserves their parameter-space p-curves in committed corpus evidence, keepsIfcEdgeCurvep-curves on neutral edge uses instead of 3D carrier slots, and bounds malformed self-referential surface graphs with a typed cycle error, and rejects indexed-arc arithmetic that would emit non-finite circles.ifc-georefdefaultsIfcMapConversionaxis components independently as specified, rejects non-finite unit-normalized transforms, and explicitly refusesIfcMapConversionScaleduntil its unequal axis factors can be represented without loss.ifc-alignmentnow rejects finite source arc parameters when derived circle frames or trim parameters overflow, and derives radial frame axes directly from source tangent and curvature sign so large global coordinates do not collapse them.Contributor links now use the repository Discussions surface, omit unpublished docs.rs and crates.io references, provide a verified immutable Git dependency for current consumers, and document an actionable confidential conduct-reporting path.
The roadmap now reflects shipped tessellated, advanced B-rep, swept, half- space, CSG, and exact-curve lowering instead of presenting those families as unimplemented.
The invalid IFC4X3 declared-schema regression now parses a synthetic
IFC4X3_ADD2document, asserts exact IFC4X3 bundle routing, and checks the typedstructure.required.missingfinding. It previously loaded an IFC4 fixture while claiming IFC4X3 coverage.Budget.max_findingsnow hard-caps report storage inReport::pushandReport::extend; previously validation marked a report truncated only after a phase had already recorded an unbounded number of findings.Duplicate GlobalIds are now reported once by the canonical
global.UniqueGlobalIdrule instead of once during structural validation and again during native-rule evaluation.The IFC4X3 inventory guard now counts only
TYPEdeclarations; a line-leadingTYPEOF(...)expression had inflated the preliminary count from 436 to 437.ifc-classificationhierarchy budgets now count every followedReferencedSourceedge and every distinct resolved entity, including the terminal classification system; revisiting an already-counted reference reports a cycle before node exhaustion.The
ifc-schema-generatetool hardcoded IFC4's entity/type counts, so it could not produce another schema's artifact and its guard could not detect the wrong source file for one. It now takes a schema selector with per-schema expected counts, pinned to the committed artifacts by a test.ifc-validateSELECT membership bounded the walk by loop iterations rather than distinct types visited. IFC4's value selects are wide -- reachingIfcMonetaryMeasureneedsIfcAppliedValueSelect -> IfcValue -> IfcDerivedMeasureValue, whose 60 members exhausted the 32-iteration budget -- so legal typed values were reported as non-members. Eight false findings across three committed fixtures. The bound is now on visited types, and an exhausted walk returns "cannot answer" instead of "not a member".test/fixtures/nurbs/ifc4_rational_bspline_curve_surface.ifcinstantiatedIfcBSplineCurveandIfcBSplineSurface, bothABSTRACT SUPERTYPEin IFC4 and confirmed invalid byifcopenshell.validate. Moved toinvalid_abstract_base_splines.ifcso the valid fixture is schema-clean and usable as validation ground truth; the lowering test that needs them now reads the invalid file.The bundled IFC4 schema artifact was missing attributes on 124 entities.
LIST [1:?] OF UNIQUE Xcontains the tokenUNIQUE, which the EXPRESS parser read as the start of aUNIQUEblock, truncating the attribute list at that point.IfcTypeProductlostRepresentationMapsandTag, so every product type reportedElementTypeandPredefinedTypetwo slots early. Also affectedIfcGrid,IfcPolyLoop,IfcEdgeLoop,IfcPropertyEnumerationandIfcPropertyTableValue. Fixed inopenbim-step 0.4.0; the artifact is regenerated and pinned by a test against known-correct layouts.ifc-scheduleimplements every plan task: work plans and schedules, tasks with task times, sequencing with signed lag, work calendars with recurrence, events, and deterministic timeline queries.ifc-costcompletes rate/component trees, nesting and control assignment, currency agreement, and tree rollups over a validated fixture.ifc-modelgains transactional authoring:Transactionstages structural edits, validates them against a projected end state, and commits atomically. Removing an entity that a surviving entity still references is refused, and a transaction opened against a stale model revision will not commit.ifc-propertiescompletesPROP-EDIT: quantity authoring helpers stage onto a caller-owned transaction, preserving each quantity's declared measure type.ifc-propertiesimplements property sets: property sets with every value family, quantities, units, templates, occurrence/type precedence, and comparison against externally computed measurements. Property values keep their declared measure type, so a length stays distinguishable from a count. SI prefixes are carried as exact decimal exponents rather than rounded factors.WR21/WR22quantity breaches are reported -- neither is checked byifcopenshell.validate.ifc-systemscompletes its plan: flow roles and direction semantics, zones with theirWR1membership rule, spatial containment vs referencing, and deterministicupstream/downstreamqueries oriented by port flow direction rather than by authoring order. Queries report when they crossed a port whose direction the file never stated, so an under-specified file cannot look authoritative.Explicit-knot polynomial/rational IFC B-spline curves and surfaces now lower into exact neutral Axiolid data. A synthetic IFC4 fixture proves parsed degrees, controls, compact knots, multiplicities, weights, and scalar-oracle evaluation.
ifc-systemsreads distribution ports and the connection network. Ports resolve through bothIfcRelNestsand the legacyIfcRelConnectsPortToElement, carrying flow direction and owning element.ConnectionGraphstates what the file says;NetworkGraphadds through-element edges so a physical run is actually traversable. Both are cycle-safe, because ring mains are normal distribution topology.ifc-systemsimplementsSYS-ROOT:systems()returns everyIfcSystemand subtype with its members resolved fromIfcRelAssignsToGroup. Systems are found by schema ancestry rather thanModel::ids_of_type, which is an exact-type index and reports no systems at all for a file whose systems are allIfcDistributionSystem.IfcZoneis included because IFC4 makes it anIfcSystemsubtype. Memberships naming absent entities, and assignments to groups that are not systems, are reported as anomalies rather than dropped.test/fixtures/synthetic-systems/synthetic_systems.ifc, the first committed fixture stating systems at all, with its generator.
Added
IfcCenterLineProfileDeflowers to a centre-line profile: an open path plus the fullThicknessacross it, resolved into a constant-width boundary by the kernel's miter offsetting. It is read with an open-path reader rather than the closed-contour one, which would have invented a closing segment and turned a bent bar into a triangle. Corpus census 105 -> 106, and every concrete profile family inIfcProfileResourcenow lowers exceptIfcArbitraryOpenProfileDef, which encloses no area by definition.
Fixed
ifc-costread the wrong attribute slots forIfcCostItemandIfcCostSchedule, skippingObjectTypeandPredefinedType. Identification, cost values and quantities were read one or more slots early, so real files produced empty or wrong results. The crate's own fixtures encoded the same mistake, which is why its tests passed.The published capability matrix is generated from the lowering source instead of maintained by hand, closing the ADR 0005 follow-up. It had drifted badly: eight shipped families were listed as planned, a warning claimed tessellated geometry was not lowered when both face sets are,
ifc-geometrywas understated by 5,472 lines, andifc-authorandifc-spatialwere missing from the census entirely.UNLOWEREDin the profile lowerer listed twelve families that in fact have live dispatch arms, so the crate understated its own profile support. The coverage gate now rejects a family that is both dispatched and declared unlowered, rather than accepting either mention as coverage.
Added
ifc-geometrylowers ten profile families: the I, asymmetric I, L, T, U, C and Z steel sections, plus ellipse, trapezium, composite and derived profiles.IfcMirroredProfileDeflowers to a mirroring transform even though itsOperatoris a DERIVED attribute no file can carry. Corpus census 93 -> 105.- Profile nesting is bounded:
IfcCompositeProfileDefandIfcDerivedProfileDefreference other profiles, so a reference cycle is refused with a typed error instead of exhausting the stack.
Fixed
- The schema coverage gate now enumerates
IfcProfileResource, the fourth geometry schema. It previously covered three, so the 22 concrete profile families were never checked and coverage claims counted only what the fixture corpus happened to contain. select::subtypegained the 22 profile rows it was missing, sois_a(.., "IFCPROFILEDEF")resolves profile families instead of answering false for all of them.
Added
lower/profile.rsdeclares each unlowered profile family with its reason, andtests/schema_coverage.rsfails if a concrete family is neither lowered nor declared. 13 families are currently declared unlowered.
Added
ifc-geometrylowersIfcExtrudedAreaSolidTapered,IfcRevolvedAreaSolidTapered,IfcFixedReferenceSweptAreaSolid,IfcSectionedSpineandIfcSweptDiskSolidPolygonal. The polygonal disk carries itsFilletRadius, which the kernel'sSweptDisknow models.
Fixed
- Trim parameters on an
IfcPolylineorIfcCompositeCurvedirectrix are segment indices, not lengths, and are no longer scaled by the length unit. In a millimetre file a parameter of2.0became0.002, collapsing the trim onto the curve's start. This affectedIfcSweptDiskSolidandIfcTrimmedCurve.
Added
ifc-geometrylowersIfcRectangularPyramid,IfcBoundingBox,IfcGeometricSet,IfcGeometricCurveSet,IfcShellBasedSurfaceModelandIfcFaceBasedSurfaceModel. Corpus census 82 -> 86.lower/bbox.rsrecomputes the world AABB from all eight transformed corners.IfcBoundingBoxis aligned to its own representation's axes, which are routinely rotated; passing corner and extents straight into a world-alignedAabbclaims a box the file never described.lower/collection.rsroutes collection members per family. Curves and surfaces stay non-dispatchable as top-level items -- a bare curve must not stand in for a body -- but are the payload inside anIfcGeometricSet, so they route there and only there, via the generatedis_asupertype table.- Two corpus inventory tests: one asserts every
IMPLEMENTEDfamily really lowers, the other walks the corpus by entity type and asserts anything that lowers is claimed. Previously the census only visited families already named in the lists, so deleting a name hid its instances instead of failing.
Fixed
IfcRectangularPyramidreadsXLength, YLength, Height. It followsIfcBlock, notIfcRightCircularCone, which putsHeightfirst; the cone ordering yields a pyramid with height and width swapped that still builds.ifc-geometrylowersIfcAdvancedBrepandIfcAdvancedBrepWithVoids.IfcAdvancedFaceattaches its support surface toFace::surface,IfcEdgeCurveattaches its support curve toEdge::curve, andIfcEdgeLoop/IfcOrientedEdgepreserve edge sharing: the fixture's eight oriented-edge uses collapse onto four shared edges, so the manifold survives instead of becoming disconnected facets.resource/topology.rsgainsVertexPoint,EdgeCurve,OrientedEdge,EdgeLoopandFaceSurfaceviews.IfcOrientedEdge.EdgeElementis slot 2, after the two inheritedIfcEdgeattributes STEP writes as*.test/fixtures/synthetic-surfaces/synthetic_advanced_brep.ifc, generated bytools/gen_surface_fixtures.py: a half-cylinder plug with one curved lateral face and two planar caps.
Fixed
- Both B-rep sense flags now compose.
IfcEdgeCurve.SameSensesets the stored edge's intrinsic sense and eachIfcOrientedEdge.Orientationflips its use of that edge. Applying only one leaves a curved solid whose face normals and edge directions disagree, which renders correctly and breaks booleans.
Added
ifc-geometrylowers the remaining exact surface families:IfcCylindricalSurface,IfcSphericalSurface,IfcToroidalSurface,IfcSurfaceOfRevolution,IfcRectangularTrimmedSurface,IfcBSplineSurfaceWithKnotsandIfcCurveBoundedPlane. Radii and axis origins convert to metres while frame axes stay unit length, so a placement keeps the U/V parameterisation that trims are taken against.A trim parameter is scaled by the basis surface's quantity kind: angle on a revolved or conic direction, length on a planar one. A file in degrees with a length factor applied turns a 90-degree patch into roughly 0.0016 of one, and nothing downstream reports it because the surface is still valid.
test/fixtures/synthetic-surfaces/— 4 generated IFC4 fixtures, plus thetools/gen_surface_fixtures.pyscript that produces them. No licensed public corpus carries curved or B-spline surfaces:ifc-lite(MPL-2.0) has none, the buildingSMART sets (CC-BY-4.0) hold only faceted breps and extrusions, and the one repo that does have them publishes no licence at all. Generating our own is the only licence-clean route, and committing the generator keeps the fixtures reproducible instead of opaque. All four passifcopenshell.validatewith zero issues.
Fixed
IfcSurfaceCurveSweptAreaSolidnow lowers end to end. ItsSweptCurvearrives wrapped in anIfcArbitraryOpenProfileDef; treating that wrapper as a profile demanded a closed contour it cannot supply. As a swept surface's generatrix it is a curve, not an area, so it is unwrapped to the curve it names. Used as an actual profile the entity is still refused with a stated reason -- closing it would fabricate a face the file never described. Corpus census rose 80 -> 81.
Added
ifc-geometrylowersIfcPlaneandIfcSurfaceOfLinearExtrusionintoGeometryNode::SurfaceandSurfaceRelation::LinearExtrusion, with a newTransform::to_geom_framethat carries a placement's own U/V axes into the kernel frame. A surface'sx/yfix its parameterisation: rebuilding them from the normal picks an arbitrary rotation about it, so the surface still renders in the same place while every trim and pcurve taken against it lands somewhere else.Depthon a linear extrusion is deliberately dropped -- the surface is unbounded in the extrusion parameter andDepthis a drawing hint, so folding it into the direction would silently reparameterise the surface.
Changed
- Relicensed repository-authored work from MIT to
AGPL-3.0-or-later; historical releases remain under their published MIT terms, and third-party material retains its own terms. IfcArbitraryOpenProfileDefnow reports a stated reason ("the neutral profile model represents closed contours only") instead of a generic "profile subtype is not lowered yet". An open profile bounds no area; closing it would fabricate a face the file never described. This is the real blocker forIfcSurfaceCurveSweptAreaSolid, whose lowering is implemented and waiting on it.
Fixed
- Removed duplicate direction normalization in surface lowering and
to_geom_frame.resource::direction::resolve_unitalready normalizes at the IFC boundary, which is where the crate's contract says it happens exactly once, so the second pass was unreachable code.
Added
ifc-geometrylowers exact curves:IfcPolyline,IfcLine,IfcCircle,IfcTrimmedCurveandIfcCompositeCurve. A trim parameter is not always a length.IfcTrimmedCurvecarries values in the basis curve's own parameterisation, which is a length along anIfcLinebut an angle on anIfcCircle. Applying the length factor to both silently rescales every arc: inswept_disk_composite_arc_crankbar.ifcthe 0.082 rad arcs would become 8.2e-5 rad on a millimetre file, and the result still renders. AnIfcVector's magnitude is likewise preserved rather than normalized away, because it scales the line's parameter rather than describing orientation.IfcCsgSolid,IfcBlock,IfcSphere,IfcRightCircularCylinder,IfcRightCircularConeandIfcSweptDiskSolidlower. A CSG solid is a wrapper and resolves to whatever itsTreeRootExpressionresolves to. CSG primitives are local by kernel contract, so theirPositionrides on anInstancenode instead of being folded into the extents, which would discard the origin offset and break any rotation. A swept disk keeps itsInnerRadius-- dropping it turns every pipe into a solid bar -- and refuses a half-open parameter range rather than guessing the missing end.With these families the committed corpus reaches 80 lowered items and an empty unsupported set: every representation item in every committed fixture now lowers into the neutral DAG.
IfcHalfSpaceSolid,IfcBoxedHalfSpace, andIfcPolygonalBoundedHalfSpacelower intoGeometryNode::HalfSpace. A half space is the infinite cutting tool IFC uses to spell "clip this solid with a plane", so lowering it is what makes the enclosing boolean resolvable:IFCBOOLEANCLIPPINGRESULTleft the unsupported set as a side effect, and the corpus census rose 67 -> 72 lowered items. TheAgreementFlagis inverted on the way through: IFC.T.selects the side the base surface normal points away from, while the neutralHalfSpace.agreementselects the normal side. Passing the flag straight through keeps exactly the half that should have been removed, and nothing downstream reports it -- the boolean still evaluates and the mesh is still watertight, the wall simply has the wrong end missing. Curved base surfaces are reported as unsupported rather than flattened to a tangent plane, which would cut along the wrong shape. The two bounded subtypes lower to their underlying half space: their bounds are clipping hints, and building a prism from an unlowered 2D boundary curve would invent geometry.ifc-geometrylowersIfcTriangulatedFaceSetandIfcPolygonalFaceSetintoGeometryNode::TriMeshandGeometryNode::PolygonMesh. Corpus census rose 64 -> 67 lowered items andIFCTRIANGULATEDFACESETleft the unsupported set. Authored n-gons and their voids survive verbatim: triangulating at read time would pick a fill rule and a tolerance on the kernel's behalf, and a face with its holes flattened into the outer loop tessellates into a solid slab, so a window silently becomes a wall. Face sets lower to meshes rather thanBRepbecause a face set carries no adjacency -- recovering topology means inferring shared edges by comparing floats, which invents information the file never had. Two indexing traps are covered by tests because both produce meshes that still render:CoordIndexis 1-based in the file and 0-based in the mesh, and aPnIndex-- at set level or on a face -- is an extra hop that permutes vertices when skipped. Normals take the frame's linear part only; sending them through the full affine transform adds the translation and breaks lighting on every product away from the origin.unreachable_products()on the facade, behindspatial+geometry-select: reports products a viewer will never draw, with the reason. Closes #5. A file can passIfcOpenShell.validatewith zero errors and still open blank -- validation asks whether the file is legal IFC, this asks whether the geometry is reachable. Three causes are detected: noIfcRelContainedInSpatialStructure(the spatial tree is how viewers reach geometry at all), a body authored only into a non-model context such asPlanView, and a representation whose context does not resolve. Openings, aggregated parts, spatial containers and representationless products are deliberately never reported: onAC20-FZK-Haus.ifc20 of 127 products sit outside the containment tree and every one of them is legitimate, so the lint finds nothing there. It lives in the facade because containment and representation contexts are sibling domain crates that ADR 0003 forbids from depending on each other.ifc-schema::ifc4(): the IFC4 ADD2 TC1 schema (776 entities, 397 types) bundled as a compiled binary artifact and cached in aOnceLock, on by default via the newifc4feature. Closes #4: consumers no longer sourceIFC4.expthemselves, hit the Latin-1 decode trap (Schema::from_express_bytesalready fixed the decode half), or reparse 372 KB of EXPRESS on every process start -- the bundled artifact is a compiled 120 KB structural table with no normative source text or prose in it.ifc-schema-generate(thegenerationfeature) regenerates the committed artifact from a user-suppliedIFC4.exp; the normative file itself is never vendored into the crate or its published archive.Schema::from_express/from_express_bytesremain the path for schemas this crate does not bundle (IFC2x3, IFC4x3, custom).product_world_transformandproducts_world_transformsare re-exported at theifc-geometrycrate root and from the facade undergeometry-select. Resolving anIfcLocalPlacementchain is the most-reused operation in any IFC consumer and the one most often reimplemented incorrectly -- composition order and unit scaling are both easy to invert. The batch form shares one placement cache, which matters because products in a storey share their whole ancestor chain.scripts/check-leakage.pyruns inscripts/gate.sh. It has existed since the documentation work but ran only by hand, which is how a trackedifc-geometry/references/directory survived undetected. 3/3 mutation probes confirm it rejects areferences/path, XSD bytes under an innocuous filename, and a PDF payload.ifc-geometrysplits into two build sizes. The new default-onloweringfeature carries the sixaxiolid-*dependencies; turning it off leaves representation contexts, plan/body selection, profiles, curves, surfaces, solids, units and placements, which readifc-modelslots and link no geometry code. Measured on the crate's own dependency graph: 26 crates with lowering, 17 without -- all eightaxiolid-*crates andglamdrop out. The facade exposes the same split asgeometry-select(selection only) versusgeometry(selection plus lowering).ifc-geometry/tests/ kernel_free_build.rsand two new cases inopenbim-ifc/tests/thin_build.rsassert it against the resolved dependency graph, so a stray unconditionaluse axiolid_*fails the gate instead of silently relinking the kernel. Existing consumers are unaffected:loweringandgeometrystay on by default. Closes #2.Opt-in recovery from damaged exports.
StepCodec::lenient()returns aStepReaderthat skips unreadable data records instead of failing the file, andModel::diagnostics()reports each dropped range, so a viewer can show "loaded, 1 record skipped" rather than silently losing data or refusing a 2.5 MB model over one truncated record.StepCodecstays strict: an authoring tool that drops entities corrupts the file it edits. Header structure and the physical-file marker remain fatal under both policies.ifc_model::Diagnostic: codec-neutral non-fatal findings carried on the model, with an optional source byte range.Advanced
openbim-stepto0.3.2for the recovery API.
Changed
geometric_productsmoved fromifc-geometry'slowermodule toinputand is now re-exported at the crate root. Asking which entities carry a shape is a slot read, so it no longer disappears with--no-default-features; the oldlower::context::geometric_productspath still resolves.Placement resolution moved from
lower::contexttoconstraint::placement. It was previously reachable only through the deeplowerpath, so it was undiscoverable, and after theloweringfeature split it did not compile at all for kernel-free consumers -- exactly the 2D consumers that need world coordinates without a solid modeller.lower::contextre-exports it, so the old path still resolves.Committed schema-derived artifacts moved from
ifc-geometry/references/toifc-geometry/data/, matchingifc-template-catalog/data/. The namereferences/is reserved for the local, unredistributable schema checkout, so a published crate must never use it. The files themselves are unchanged and were never a licensing problem -- they carry structural facts (slot indices, declaration names) and this repository's own ownership mapping, with no EXPRESS source text -- but the directory name defeated the detector that exists to catch real leaks.data/NOTICE.mdrecords the reasoning. The localreferences/tree is now gitignored so the detector sees only the publishable tree.Behavior change.
select_plan_representationnow requires a drawable identifier and a plan context, instead of letting the context win outright. ArchiCAD authorsBox/BoundingBoxshape representations inside aPLAN_VIEWsub-context, so the old context-first rule returned a bounding box and never consultedPLAN_IDENTIFIERS. OnAC20-FZK-Haus.ifcthat was 107 of 253 shape representations, and every plan lookup came back a box. Authorial intent now selects between drawable candidates rather than making a box drawable.This returns fewer answers, not just better ones: on that file, products resolving a plan representation drop from 121 to 34 (14
Annotation, 13Axis, 7FootPrint, and no non-drawable picks). The 87 products that lose an answer genuinely have only a bounding box, andNoneis the documented contract for "no drawable plan geometry" -- but a consumer that was drawing those boxes will now draw nothing for them.ifc-geometryrepresentation contexts:RepresentationContextreadsIfcGeometricRepresentationContextandIfcGeometricRepresentationSubContext-- identifier, type, parent, target scale, and a typedTargetViewthat preserves unknown enumeration constants instead of flattening them.plan_contextsfinds the sub-contexts a drawing is authored into.DERIVED attribute inheritance: a sub-context redeclares six inherited attributes, which real files write as
*meaning "read this from my parent".precision,world_coordinate_system,coordinate_space_dimensionandtrue_northresolve the parent chain; reading the slot directly yields the marker and silently loses the project's precision and placement. See ADR 0009.select_plan_representation: the inverse ofselect_shape_representation. Prefers an explicitPLAN_VIEWcontext, thenPlan/Annotation/FootPrint/Axis, and returnsNonefor a solid-only product rather than offering a body to draw flat.ifc-spatial: containment and objectified relationship traversal. Builds the project/site/building/storey/element tree fromIfcRelAggregates,IfcRelContainedInSpatialStructureandIfcRelNests, answering "which elements are on this storey" and its inverse. Tolerates real exports -- omitted levels, elements on the building, duplicate storeys, dangling references and containment cycles -- reporting defects throughorphans()anddangling(). Reached through the facade's newspatialfeature. See ADR 0008.ifc-model::ReverseIndex: on-demand target-to-referrer index recording the attribute slot each reference sits in, which is what distinguishes the two ends of an objectified relationship.ifc-modelbounded traversal:depth_first,breadth_firstandfind_cyclewith explicitBudget/Stopreporting, so a malformed file truncates with a diagnosis instead of hanging the caller.ifc-author: schema-checked entity construction. Build an entity by naming its attributes and letifc-schemaresolve STEP slot positions, instead of hand-placing positional values. Refuses unknown entities and attributes, duplicate sets, missing required attributes, declared-type and aggregate mismatches, and malformed GlobalIds. Reached through the facade's newauthorfeature. See ADR 0007.A documentation site (VitePress) published to GitHub Pages, covering architecture, a conservative capability matrix, ADRs, a roadmap, and end-to-end use-case guides.
Six architecture decision records covering the domain/codec-free entity graph, the codec trait, borrowed domain views, the Axiolid geometry boundary, scaffold-module semantics, and thin facade defaults.
openbim-ifc/tests/docs_examples.rs, which compiles and runs every Rust example shown in the documentation so published code cannot drift.scripts/sync-changelog.py, generating the documentation changelog page from this file;scripts/gate.shfails on drift.scripts/check-leakage.py, rejecting standards material (XSD, PDF,references/) from the published site.
Fixed
Model::insertno longer corrupts the type index when it replaces an existing entity: the id was appended unconditionally, so re-inserting listed it twice under the same type, and replacing an entity with one of a different type left it listed under both.ids_of_typeandtype_histogramreported those duplicates.
Changed
- Advanced
openbim-stepto0.2.1for strict mandatory-header validation, line/print-control handling, low-line keywords, and lossless string escapes. - Delegated generic ISO 10303-21 STEP syntax and ISO 10303-11 EXPRESS parsing to
openbim-step; IFC retains thin model, schema-version, and validation adapters. - Extracted the IFC family from
openbimrs/openbiminto its canonical standalone repository while preserving relevant source history. - Added an independent Cargo workspace, CI workflow, verification gate, project documentation, and self-contained regression fixtures.
- Made release-critical package metadata explicit across the nested-workspace boundary.