Changelog
Each crate in the family owns its own changelog and is versioned independently: a release of one does not imply a release of any other. This page collects them, newest version first, with the crates that changed in each. Entries follow Keep a Changelog.
History up to and including 0.2.0 -- when the family was released in lockstep -- is archived in the repository changelog.
[Unreleased]
ifc-model
Added (#465)
Inverse,Restored,InverseError: the recorded inverse of a run of model writes (Inverse::record,Model::begin_recordingandend_recording). Applying one restores the model exactly (file order, type-index order, attribute counts, header, next free id) and returns the redo.HistoryandHistoryError: a bounded undo and redo stack of inverses;History::trackrecords any change as one entry.Transaction::commit_reversible,Transaction::set_headerandTransaction::header.
Changed (breaking) (#465)
Model::header_mutbumps the revision. ATransactionopened before aheader_mutedit is now refused at commit withConflict::StaleRevision; set the header first, or stage it withTransaction::set_header.- Revisions are process-unique: every change draws a new value from one process-wide counter, so values are increasing but no longer consecutive, and two models never share a revision once either was written. A
TransactionorInversemade against one model is refused by another. A clone keeps its source's revision until either side changes. Model::set_attributeswith an empty edit list changes nothing and no longer bumps the revision.Model::clonedoes not copy an open recording.
Semver: the revision changes can refuse code that committed before, so this release is 0.4.0, not a patch.
ifc-template-catalog
Fixed (#537)
- The XML importer trims
DataType@typeandreftype: about 100 IFC2X3 entries (IfcBoolean) and one IFC4X3 entry (IfcDuration) carried trailing spaces and so named no type. The embedded container, the IFC2X3 and IFC4X3 snapshot pins and TSV exports are regenerated from the pinned sources (source digests unchanged).
Semver: patch.
ifc-validate
Added (#537)
property.undeclared_memberandproperty.member_typewarnings (bSI PSE001): for aPset_/Qto_set the release's catalog defines, a property or quantity the catalog's template does not declare, and a set kind, entity form or value type that differs from it. A value type passes when it is the declared one or a defined type aliasing it. Names match exactly; a case-only difference is mentioned in the message.PropertySetCatalog::template, a provided method returning the neutralCatalogSet(CatalogSetKind,CatalogMember,CatalogMemberForm,CatalogQuantityKind). The default returnsNone.FindingDetail::CatalogMember(CatalogMemberDetail)withCatalogMismatch.
Changed (#537)
A catalog whose
templategives none for a set it defines gets one unsupported finding per new rule id ("the members of N catalog sets were not checked"); a declared value type the release's tables lack gets one unsupportedproperty.member_typefinding. Neither is silent.A declared SELECT type (
IfcValue, as inBendingParameters) takes any defined type its closure holds, and a single value declared with an entity type (the IFC2X3*PHistorysets:IfcTimeSeries) is written as anIfcPropertyReferenceValueto that entity, which is checked against it.
Known false-positive source: some catalog templates disagree with the schema or with each other (Qto_BuildingElementProxyQuantities, ProcessItem sets); those are catalog data issues tracked in #553, not fixed here.
Semver: minor (0.8.0): runs with a template-giving catalog report new warnings, and PropertySetCatalog, FindingDetail and the new types grow the public API.
openbim-ifc
Changed (breaking) (#465)
- Through the re-exported
Model:header_mutbumps the revision, so aTransactionopened before a header edit is refused at commit withConflict::StaleRevision; revisions are process-unique rather than per-model counters, so a transaction or inverse made against another model is refused (ifc-model0.4.0).
Added (#465)
- Re-exports
History,HistoryError,Inverse,InverseErrorandRestoredfromifc-model:History::trackaroundapply_authoring(or any change) makes it undoable with byte-identical restoration.
Changed (#559)
- The
GlobalIds a property edit derives for the sets and relationships it creates derive from a caller seed, the object'sGlobalId, the set name, the record's role and its count among the batch's records of that object, set and role, no longer from the model's next free id. The same edits under the same seed give the sameGlobalIds after a write/read cycle that shifts the next id, so a journal of property edits replays to the same identities. stage_property_editsandapply_property_editstake thatseed, and so does every write that may create a set:SelectorIndex::stage_set_value,selector::set_value,SelectorIndex::plan_text_edits,selector::apply_text_edits,sheet::plan_importandsheet::import(an attribute write ignores it).fresh_seedis available in every build, no longer only withauthoring.
Added (#559)
PropertyEditFailure::SeedReused, raised when a derivedGlobalIdis held by an entity the batch leaves in the model (the seed named an earlier batch's records). A seed may edit other objects or sets, and may be used again once its records are removed.
Semver: breaking (minor while 0.x), released with #548 in 0.38.0: the property-edit, selector-write and sheet-import entry points take a seed, the same edit of the same file no longer gives the same GlobalIds, and SeedReused is a new variant of the #[non_exhaustive] PropertyEditFailure.
Changed (#548)
- The
GlobalIds a batch derives (anIfcRootcreated without one, every relationship a builder writes, copies) derive from the seed, the operation's position in the batch, the created entity's type and its count among that type in the operation, no longer from the entity id; anapply_appendderives from the seed, the type and the count. The same seed and operations give the sameGlobalIds after a write/read cycle that shifts the next id, so a journal of batches replays to the same identities. AnAssignTypethat joins an existing relationship derives none.
Added (#548)
AuthoringFailure::SeedReused, raised when a derivedGlobalIdis held by an entity of the model (the seed named an earlier batch). A seed whose entities were all removed may be used again.
Semver: breaking (minor while 0.x): a fixed seed gives other GlobalIds than 0.37.0, and a batch that reuses a seed may now be refused; SeedReused is a new variant of the #[non_exhaustive] AuthoringFailure. The hosts draw a fresh seed per batch (the browser from Math.random).
Fixed (#534)
AuthorOp::AssignTypeon a type that already has anIfcRelDefinesByTypeadds the objects to that relationship'sRelatedObjectsinstead of creating a second one, which brokeIfcTypeObject.Types SET [0:1](ObjectTypeOfin IFC2X3). The op produces the existing relationship's id, andowner_historyapplies only to a relationship that is created. An object typed by another type is still refused unlessreplace. A relationship whoseRelatedObjectsis not a list is refused withInvalidRelationship, not rewritten.
Added (#537)
ReleaseCataloggivesifc-validatethe member templates of the embedded or installed PSD/QTO catalog, sovalidatereportsproperty.undeclared_memberandproperty.member_typewarnings for the members of a catalog set. A set kind the build does not know gives no template, and its members are reported as not checked.
Semver: minor; it re-exports ifc-validate 0.8.0 and its new findings.
Fixed (#537)
- The property writer's template check accepts any type a declared SELECT (
IfcValue) closes over, asvalidatedoes; it refused them before.
Changed (#539, tests)
Semver: no release on its own. No code change.
- The #469 corpus test
length_unit_corpus.rssums mesh volumes about each body's first vertex rather than the world origin, where triple products at georeferenced coordinates cancelled catastrophically. The 29 products it skipped as ill-conditioned (blamed on booleans decided by the last bit) were that measuring artefact: the skip list is removed, and all 181 meshed products are compared and agree after converting to feet.
openbim-ifc-cli
Changed (#559)
editandimport-tablederive theGlobalIds of the sets and relationships they create from a seed drawn per run, no longer from the file's next free id. Behaviour change: the output ofeditandimport-tableis no longer reproducible between runs; the same edit of the same file gives newGlobalIds on each run.
Semver: patch (commands, flags, exit codes and documents unchanged; only the GlobalIds of newly created records differ per run).
Added (#538, EXPRESS rules)
validate --express FILE.exp(repeatable, one file per release) evaluates every WHERE, type and global rule of each file's release from the EXPRESS text you supply. A file with no matching--expressis refused with the new kindexpress-mismatch; a file that does not parse isinvalid-express. Both exit 2.- The JSON summary gains
indeterminate, always present; the human summary adds it when--expressis given. An indeterminate rule never fails the run.
Semver: minor (additive; exit codes keep their meaning).
Changed (#537)
validatealso reports theproperty.undeclared_memberandproperty.member_typewarnings for the members of a catalog set, when the build carries the property catalog.
Semver: no code change; patch when released.
[0.37.0] - 2026-10-20
openbim-ifc
Added (#479, copy and append)
AuthorOp::CopyProduct { product, options }copies anIfcProductorIfcTypeObjectwith whatRemoveProductwould remove with it, by the #478 cascade's own reference count, and shares what it would keep. Copied, with newGlobalIds: the product; itsObjectPlacement(relative to the same parent;CopyPlacement::Offsetmoves it in the parent's frame,CopyPlacement::Atreplaces it); its representation byCopyRepresentation's rules (a type's representation maps with their origins); the property and quantity sets only it is defined by; its material usage; its ports; and, byCopyProductOptions::openingsandfillings(both on), its openings and their fillings, placed relative to the copied placements. Shared, the copy joining the original's relationship: its type assignment, shared property sets, classification, document and library references, groups, layers and, bycontainerandaggregate(both on), its spatial container and its whole. Relationships that would bind another object (connections, space boundaries, parts, a type's occurrences) are skipped and reported. Entities aUNIQUErule binds (IfcPropertyEnumeration,IfcApplication) are always shared.AuthorOp::CopyEntity { entity, policy }: a deep copy along attributes by an explicitCopyPolicy(classes always copied, always shared; otherIfcRoots and definitions shared unless asked).CopyReport(copied, reused, joined, skipped, regenerated, each original with its copy) inAuthoringOutcome::copiesandStagedAuthoring::copies.apply_appendandstage_appendcopy entities of another model with their closure: what they reference, their property sets, type, material, classification, openings, fillings and ports, and what attaches to their geometry; relationships to objects not copied are skipped. Equal units, contexts, owner history, materials and styles are reused (ReuseOptions);GlobalIdModekeeps, merges by identity (joining the target's relationships) or regeneratesGlobalIds. Attributes move by name between releases.AuthoringFailure::UnsupportedCopy,IncompatibleRelease(a type, attribute or enumeration constant the target release lacks; never dropped) andUnitMismatch(differing length or plane angle units; the message points at unit conversion, #469).
Changed (#479)
- The operation options moved from
authoring/op.rsinto their own module under the 800-line cap; every public path is unchanged.
Semver: additive (AuthorOp, AuthoringFailure, AuthoringOutcome and StagedAuthoring are #[non_exhaustive]): minor.
Added (#461, #511)
- Feature
validate-express:ifc::validate::express, the EXPRESS rule evaluator ofifc-validate(every WHERE rule, type rule and global rule of a release, from the.exptext the caller supplies). Infull. ifc::validateis a module re-exportingifc-validatewhole. Withproperty-catalogorproperty-catalog-runtime, itsvalidate,validate_with,validate_declaredandvalidate_with_optionscheckPset_/Qto_set names against the declared release's catalog (ReleaseCatalog): a set the catalog does not define is aproperty.reserved_set_namewarning. Without a catalog the names are reported once as unsupported.
Semver: minor.
Added (#469)
convert_length_unitandstage_length_unit_conversion: rescale every length-dimensioned value of a model to a new project length unit (an SIMETREwith any prefix, orinch,foot,yard,mile,US survey foot) as one checked transaction. Which values are lengths, areas and volumes comes from the declared release's tables (IFC2X3, IFC4, IFC4X3), through every attribute, aggregate and typedSELECTvalue: geometry, placements, profiles, quantities and property values. Values with a unit of their own keep it. EveryREALandIfcParameterValueattribute of the three releases is reviewed (a test fails on an unreviewed one):IfcGeometricRepresentationContext.Precisionrescales, light attenuations rescale inversely, parameter values follow their curve or surface (kept on lines, conics, polylines, B-splines, clothoids and composite curves of those; rescaled on planes and along a cylinder's axis). Georeferencing keeps map coordinates in aMapUnitand rescalesScale; IFC2X3ePSet_MapConversionlikewise. Areas and volumes follow the length unit by default (LengthUnitOptions). Refused, each by name, with nothing written (LengthUnitError::Refused): parameter values on curves whose parameter space IFC leaves undefined (other spirals,IfcCurveSegment,IfcOffsetCurveByDistances), p-curves on curved surfaces,IfcPolynomialCurvecoefficients, texture generator parameters, untyped numbers in aSELECT, contexts with their own units. TheLengthUnitReportlists what was rescaled and kept. Verified over the fixture corpus: a millimetre-metre round trip restores every value, compiled geometry agrees in metres after converting to feet, and validation findings are unchanged.
Semver: minor (additions).
Added (#472, #474, values by path as text and tables)
ifc::selector::value_textwrites aSelectedValueas the text of one cell (TRUE, the shortest decimal of a real,#12, a null text), andSelectorIndex::plan_text_editsplans manyTextEdits (entity, path, text) as one transaction: each text read as a value of its target's declared type (attributes) or stated type (properties),IFCLABEL(F90)naming one where nothing states it; a value already held left alone; every refusal collected as anEditRefusalnaming its edit. AnEditPlanlists eachPlannedChange(target, before, after) and commits as one transaction, refused if the model changed since.apply_text_editsplans and commits.SelectorError::InvalidTextandSelectorError::ConflictingWrite(the enum is#[non_exhaustive]).ifc::sheet(featuresheet, impliesselector):exportwrites entities by value paths as aSheetkeyed byGlobalIdorid(KeyColumn,SheetOptionswith a null text);plan_importreads a sheet back by key into anImportPlanofCellChanges, or everySheetRefusalby row and column (header spans, missing, empty, unknown, ambiguous and repeated keys, ragged rows, per-cell write refusals);importcommits it as one transaction. An unchanged sheet plans no change and commits nothing.Sheet::readandSheet::writetake CSV (RFC 4180) and TSV with escapes; XLSX with featuresheet-xlsx, throughrust_xlsxwriter0.100 andcalamine0.37 (pure Rust, on thezip9 andquick-xml0.42 already in the tree, no default features), every cell written as text.fullincludessheet-xlsx.
Semver: minor (additive).
Changed (#512)
product_curvesreads a representation that also holds text or a fill area. A dimensionIfcAnnotation(its lines asIfcPolylines, its value as anIfcTextLiteralWithExtent) was refused as a whole; its lines now come back as polylines, and eachIfcTextLiteral,IfcTextLiteralWithExtentandIfcAnnotationFillAreaof the representation is listed in the newProductCurves::skipped(aSkippedItem: the item and itsNonCurveItemkind), not drawn and not a refusal. Read those items withifc_style::StyleView. Any other item that cannot be lowered still refuses the product.SkippedItemandNonCurveItemare re-exported fromifc-geometry.
Semver: minor. ProductCurves is #[non_exhaustive] and gains a field; products that were refused now return their curves.
Changed (#467)
validate::validate_declaredrefuses a file declaringIFC4X3_TC1orIFC4X3_ADD1asValidateError::UnpublishedSchema, asifc_validate::validate_declareddoes: a release buildingSMART publishes no EXPRESS for, never validated as IFC4X3 ADD2.
Semver: minor, following ifc-validate.
[0.36.0] - 2026-10-18
openbim-ifc
Changed (#506, #509)
- Requires
ifc-geometrywith #506 and #509 (axiolid-mesh-compile0.3.28).product_curvesreturns certified polylines for trimmed, composite, offset and intrinsic curves; a curve the reference compiler has no certified polyline for is refused asGeometryError::CompilationRefusedwith its reason, naming the product.product_meshescompiles advanced B-reps whose edges cover part of their line, circle or ellipse, with eitherSameSense. geometry::GeometryError::PolylineUnsupportedandgeometry::compile::UnpolylinedCurve, re-exported fromifc-geometry, are gone.
Semver: minor, following the dependency.
[0.35.0] - 2026-10-16
openbim-ifc
Changed (#515, #519)
- Requires
ifc-geometrywith #515: everyIfcPolygonalBoundedHalfSpacelowers to Axiolid'sBoundedHalfSpaceAlongZ, its prism alongPosition's Z (axiolid/kernel#321). Oblique clips, such as gable walls under sloped roofs, mesh and compile exactly, non-convex and arc boundaries included;product_meshescuts small clips over tall subjects to their full depth (axiolid/kernel#325, #519).product_graphslabels a payload with such a clip wire format 1.4 (axiolid-model0.3.14); every other payload keeps its version and bytes.
Semver: minor, following the dependency.
[0.34.0] - 2026-10-14
openbim-ifc
Added (#458, curves and surfaces as output primitives)
Semver: minor (additive; ProductMesh is #[non_exhaustive]).
product_curves(model, products, purpose)(featuremesh): each product's curves for aRepresentationPurposeasProductCurves(world placement, oneProductPolylineper curve: the IFCcurve,f32positions relative to the placement,closed,chord_boundandchord_requested).Plantakes the drawing curves (Plan, thenAnnotation,FootPrint,Axis), andAxis,FootPrint,Annotationone identifier. A trimmed, composite, offset or intrinsic curve refuses the product withGeometryError::PolylineUnsupporteduntil the reference compiler polylines it (#506).product_meshes_for(model, products, purpose): the surfaces and solids of another representation, such asSurface, as one mesh; its curves are left toproduct_curves.product_meshesis it withBody.ProductMesh::closure:Solid,Surface(an area, no volume) orUnknownfor an empty mesh. Only a Body mesh resolves a colour; a mesh of another representation carriesOk(None).RepresentationPurposeandselect_product_representationare re-exported at the root.
Changed (#458)
- A Body whose items mix curves with solids or surfaces now meshes those and leaves the curves to
product_curves; before, the reference backend refused the whole Body. A Body with no curves meshes exactly as before.
[0.33.0] - 2026-10-14
openbim-ifc
Changed
- Requires
ifc-author0.3.2, whose builder accepts nested aggregates that the #481 representation ops write.
Added (#462, INVERSE attributes by name)
attribute_value_by_name(&model, schema, &reverse, id, name)reads an explicit attribute (AttributeValue::Explicit) or anINVERSEone (AttributeValue::Inverse): every referrer whose type is theFORentity or a subtype in the declared release and whoseFORattribute (the slot that release gives it) holds the entity, directly or as an aggregate member, read from aReverseIndexthe caller builds once.InverseValue::Aggregate(ids)for aSETorBAG,SET [0:1]included (zero or one id, never unwrapped), in ascending id order;InverseValue::Single(Option<EntityId>)for a non-aggregate inverse (VoidsElements, IFC2X3'sIfcGroup.IsGroupedBy), refused withNamedAttributeError::InverseCardinalitynaming every referrer when more than one references it. Both enums are#[non_exhaustive].inverse_slots(schema, type_name)andinverse_slot(..): everyINVERSEattribute of a type, inherited first, asInverseSlot(name,FORentity and attribute, aggregation withlower()/upper(), declaring entity); a subtype's redeclaration replaces the inherited one. The bundled tables' answers equal the normative EXPRESS's for every entity of IFC2X3, IFC4 and IFC4X3 (a spec-backed test).total_inverses(&reverse, id): the distinct entities referencing one (IfcOpenShell'sget_total_inverses).ReverseIndexandReferrerare re-exported at the root;ReverseIndex::referrersis theget_inverse(.., with_attribute_indices=True)equivalent.
Changed (#462)
attribute_slot,attribute_by_name,set_attribute_by_nameandattribute_typerefuse anINVERSEname with the newNamedAttributeError::InverseAttributeinstead ofUnknownAttribute;UnknownAttributenow reads "has no attribute".
Semver: additive (NamedAttributeError is #[non_exhaustive]), a minor release while 0.x.
Added (#481, shape representations)
AuthorOp::ShapeRepresentation { context, items, identifier, representation_type }assembles anIfcShapeRepresentationof existing items in a geometric representation context. A givenRepresentationTypeis checked against the items by the declared release'sIfcShapeRepresentationTypes(CorrectItemsForType, IFC2X3WR24), transcribed per release family (IFC2X3; IFC4, IFC4X1, IFC4X2; IFC4X3), withIfcCurve.Dimderived asIfcCurveDimdoes; without one, the most specific listed type admitting every item is inferred. A label the release does not list is written as given (its rule returns?). The identifier defaults to the context'sContextIdentifierand is required from IFC4 on (HasRepresentationIdentifier);CorrectContextandNoTopologicalItemare checked.AuthorOp::AssignRepresentation { representation, to }: to anIfcProduct, appended to itsIfcProductDefinitionShape(created when missing; produces it); to anIfcTypeProduct, wrapped in a newIfcRepresentationMapat the origin and appended toRepresentationMaps(produces the map). A representation already in a product shape, representation map or shape aspect is refused (IfcShapeModel.WR11).AuthorOp::UnassignRepresentation { representation, from }: off the product shape (an emptied one goes, with its shape aspects, and the product'sRepresentationis unset) or the type (its map goes with its origin); the representation stays, unassigned. Reports what went.AuthorOp::CopyRepresentation { representation }: a copy of every representation item and profile definition it reaches, and of the styled items and colour or texture maps attached to them; the context, styles, presentation items, the type maps mapped items map and every other resource are shared. The copy joins the original's layers.AuthorOp::RemoveRepresentation { representation }: taken off its uses, then removed by the #478 cascade, shared items, profiles, points and styles kept and reported.AuthoringOutcome::reportsandStagedAuthoring::reportscarry the report of an unassignment or a representation removal.AuthoringFailure::RepresentationTypeMismatch,NoRepresentationType,UndecidableRepresentationType,MissingRepresentationIdentifier,InvalidRepresentationItem,SharedProductShapeandNotAssigned.
Semver: additive (AuthorOp and AuthoringFailure are #[non_exhaustive]): minor.
Added (#464, selector query language)
ifc::selector(featureselector; infull): a selector grammar, version 1.0 (GRAMMAR_VERSION), close to IfcOpenShell'sutil.selector: class facets (subtypes included) andGlobalIdfacets with!negation, explicit attribute facets, property and quantity facets (Pset_WallCommon.FireRating=F90, names or/patterns/),type,material,classification,location(aliascontainer) andgroupkeywords,query:value paths, comparisons=,!=,*=,!*=,>,>=,<,<=against text, numbers,NULL,TRUE,FALSEand patterns,,facet lists,+unions and/* */comments.Selector::parseandValuePath::parsegive a typed AST that prints back in canonical form; everyParseErrorcarries the byte span it refuses.SelectorIndexevaluates a selector (select,select_from) over the model's type index, aGlobalIdIndex, the exact resolver'sPropertyIndex(featureproperties;FeatureDisabledwithout) and one scan per relationship kind, each built on first use. A class or attribute the release does not declare is refused with its span before any entity is read; contradictory or dangling relationships are refused, never skipped. Results are in ascending id order.- Values by path (
SelectorIndex::value,get_value): attributes, property sets and properties,type,material(s),container,parent,space,storey,building,site,occurrences,classification,group,class,id,predefined_type,countand list indices, asSelectedValues. - Writes by path (
set_value,SelectorIndex::stage_set_value,set_targets,stage_attributes,property_edits): an attribute of every entity the path reaches, checked against its declared type in the release, or a property through the checked property edit, staged on one transaction;SelectorErrornames every refusal. - Depends on
regex-lite(optional, withselector).
Semver: minor (additive).
Added (#480, type propagation on assignment)
AuthorOp::AssignTypetakesoptions: AssignTypeOptions. Withmap_representations, each of the type'sRepresentationMapsis mapped onto every object as anIfcShapeRepresentation(the map's context andRepresentationIdentifier,RepresentationTypeMappedRepresentation) holding oneIfcMappedItem, whoseMappingTargetismapping_targetor a fresh identityIfcCartesianTransformationOperator3D(2Dfor a 2DMappingOrigin); the representations join the object'sIfcProductDefinitionShape, created when it has none. Withmaterial_usage, anIfcMaterialLayerSetUsage(LayerSetDirectionfromlayer_set_directionor the class:AXIS2forIfcWall,AXIS3forIfcSlabandIfcPlate, as the IFC4 ADD2 TC1 documentation fixes them;POSITIVE; offset 0) or anIfcMaterialProfileSetUsage(noCardinalPoint) is derived from the type's set, one per object in its ownIfcRelAssociatesMaterial; any other material applies through the type.predefined_type: PredefinedTypeModeisIgnore(default),Validate(refuse an object whose ownPredefinedTypeis set, notNOTDEFINEDand differs from the one the type provides;USERDEFINEDcomparesObjectTypewith the type'sElementType) orSync(unset the object's, and itsUSERDEFINEDObjectType, where the type provides one).replaceunassigns each object's previous type: it leaves itsIfcRelDefinesByType(removed when emptied), and the representations mapped from the previous type's maps and the usage derived from its set are removed with what only they use, by the #478 cascade's ownership rules.AssignTypeOptions::propagate()sets the three propagations.AuthorOp::ContainandAuthorOp::Aggregatetakeoptions: RelateOptionswithreplace, which moves an object out of its previous container or whole instead of refusing it.AssignTypeOptions,RelateOptions,PredefinedTypeMode,LayerAxis.AuthoringFailure::RepresentationConflict,UnsupportedRepresentationMap,MaterialUsageUndeterminedandPredefinedTypeConflict; a material on an object that already has one isAlreadyRelatedwithIFCRELASSOCIATESMATERIAL.
Refusing a second type, container or parent stays the default, and every option is off by default: a batch that sets none writes what it wrote before. Semver: minor while 0.x. The three operations gain a field, so a struct literal of them needs options (AssignTypeOptions::new(), RelateOptions::new()); new variants of #[non_exhaustive] enums and new #[non_exhaustive] types are additive.
[0.32.0] - 2026-10-13
openbim-ifc
Added (#478, cascading delete and purge)
AuthorOp::RemoveProduct { product, options }removes anIfcProductorIfcTypeObjectwith everything it solely owns: itsObjectPlacement, its representation down to the items nothing else uses, the property and quantity sets only it is defined by with their properties, its material usage (not the material definitions), its openings (each with the same cascade) and its ports (nested, or connected in IFC2X3), and every relationship left without an end. "Solely owned" is a reference count over the batch's view (the base model'sReverseIndex, corrected for the batch's own changes), taken to a fixed point; a relationship that only loses a list member and a presentation layer assignment do not count, a styled item and a shape aspect go with what they attach to. Shared data is kept: a property set another object uses, a type's representation maps, a style or point used elsewhere; so are related objects (the type, the container, the fillings of a removed opening) and definitions (material definitions, classification references, units, contexts, the owner history).RemoveProductOptions(#[non_exhaustive]):rebase_placementsre-bases placements relative to a removed placement onto its nearest kept ancestor with the composedIfcAxis2Placement3Dtransform, so nothing moves;untype_occurrenceslets a type object with occurrences go, removing itsIfcRelDefinesByType.AuthorOp::Purge(PurgeOptions): without classes, every resource entity noIfcRootreaches, nor annotates something reached (styled items, layer assignments, shape aspects, resource relationships, coordinate operations); with classes, the unreferenced entities of those classes.IfcRoots (include_roots;IfcProjectonly when named), the owner history with its actors (include_owner_history) and, by default, representation contexts are kept unless asked.RemovalReport(removed,keptwith aKeepReasonand theusersof a shared entity,detached,rebased),RemovedEntity,KeptEntity,KeepReason(Shared,RelatedObject,Definition,Protected):AuthoringOutcome::reportsandStagedAuthoring::reportscarry one per operation, so staging a batch is the dry run.AuthoringFailure::PlacementHasDependents,TypeHasOccurrencesandUnsupportedRebase.
Changed (#478)
- The authoring batch reads who references an entity from the base model's
ReverseIndex, built once per batch on the first removal, instead of scanning the model per lookup.
Semver: additive (new variants of #[non_exhaustive] enums, new fields of #[non_exhaustive] structs), a minor release while 0.x.
Added (#450)
- ifcZIP, behind the new
zipfeature (on infull):ifc::ifczipreads an archive holding one STEP or ifcXML model at its root (buildingSMART implementation agreement CV-2x3-154), the inner format and the ifcXML layout detected from the content, through the existing codecs (read,extract,read_entry), and writes one Deflate entry (write,pack), reproducibly. Refusals are typed (IfcZipError):Malformed,NoModel,SeveralModels,UnknownInnerFormat,Encrypted,UnsupportedCompression(anything but Stored and Deflate),BudgetExceededandCodecNotCompiled. Decompression is bounded by aBudget, absolute (4 GiB) and as a ratio to the compressed size (200, above 1 MiB), checked against the declared size and again while inflating. ifczip::IfcZipCodecputs ifcZIP behind theCodecinterface:codecs()lists it andread_pathreads.ifczip(an archive is recognized by its ZIP signature; without the feature it is refused asWrongFormatnaming the feature).xml_layout::declared_xml_layoutandDeclaredXmlLayout(featureifcxml): the ifcXML layout a document's root declares, and whether its attributes are named by position, for readers handed a document without its layout.- Dependencies:
zip9 with every default off and Deflate alone, throughflate2's pure-Rust miniz_oxide backend; only withzip.
Semver: minor (additive; new feature and items).
Added (#449)
- Feature
colour(geometry-select,style,material):product_colours(model, products)andColourResolver(product_colour,item_colours) resolve the surface colour of each product's Body. Each item takes its ownIfcStyledItem, then its styled presentation layer (a boolean result itsFirstOperand's, a mapped item its enclosingIfcMappedItem's), then theIfcMaterialDefinitionRepresentationof the material the product, or its type, is made of. AnIfcMaterial, a layer or profile and its material, layer and profile sets and their usages naming one material, IFC2X3'sIfcMaterialListof one material, and constituent sets by item (the constituent whoseIfcShapeAspectnames the item) resolve. A set of several materials isColourError::AmbiguousMaterial, never its first member; Body items that differ areColourError::ItemsDiffer. The colour carries its source (ColourSource: styled item, layer, material). ProductMesh::colour(meshnow impliescolour): each mesh's colour, or its typedColourError, which never refuses the mesh. Positions and indices are unchanged.
Semver: additive (ProductMesh is #[non_exhaustive]), a minor release while 0.x.
Added (#463, GlobalIds, types and openings)
GlobalIdIndex::build(&model, schema): everyIfcRootbyGlobalId, read once (O(n), onlyIfcRoots decoded; the slot looked up by name in the release's table).get(&Guid)isOk(Some(id)),Ok(None), orErr(DuplicateGlobalId { global_id, entities })naming every holder in file order: a duplicatedGlobalIdis never answered with one of them.duplicates()lists them all andinvalid()theIfcRoots whoseGlobalIdis unset or malformed.by_guid(&model, schema, &guid)answers one lookup by a single scan. Behindschema-api.type_of(model, schema, occurrence),occurrences_of(.., type_object),openings_of(.., element),fillings_of(.., opening)andvoided_element_of(.., opening), read forward fromIfcRelDefinesByType(in IFC2X3 too, whoseIsDefinedBymixes it with property sets),IfcRelVoidsElementandIfcRelFillsElement, slots by attribute name. Each refuses withObjectRelationError(#[non_exhaustive]):MissingEntity,WrongEntityType,Malformed,MissingReference, andAmbiguousfor two types of one object or two hosts of one opening, where the schema allows one. Behindschema-api.new_guid(): a random RFC 9562 version-4GlobalIdfrom the operating system (getrandom0.3, already linked natively throughahash), orGuidError(#[non_exhaustive]). Onwasm32-unknown-unknownthe newjs-randomfeature links getrandom's JavaScript backend (crypto.getRandomValues, noRUSTFLAGSneeded); without it there the call refuses withGuidError::NoRandomSource.name_based_guid(parts), the version-8 name-basedGlobalIdthe property edit and authoring already derived theirs with, is public; its output is unchanged and pinned by a test.Guidis re-exported at the root.
Semver: additive, a minor release while 0.x.
Changed
georeferencingreads IFC2X3 (#451): theePSet_MapConversionandePSet_ProjectedCRSproperty sets of the project (or else of each site) resolve with the project length unit to the sameProjectToMapas IFC4's entities. An IFC2X3 file used to be refused withUnsupportedSchema; without the sets it now has an empty list, and a malformed or incomplete set is refused with the newGeorefErrorvariants. Property sets of those names in an IFC4 or IFC4X3 file are not read. Requiresifc-georefwith #451.
Semver: minor. A release that was refused now resolves.
[0.31.0] - 2026-10-13
openbim-ifc
Changed
- Requires
ifc-geometry0.25: stations on IfcOffsetCurveByDistances bases that lower to OffsetsAtStations lower with seam windows, and an offset of an offset is refused by its own name (#443, part). Semver: minor, following the dependency.
[0.30.1] - 2026-10-20
ifc-geometry
Changed (#525, #528: axiolid-mesh-compile 0.3.31)
Semver: patch. No public item changes; an optional compile-feature floor and a dev floor rise.
- Requires, behind
compile-reference-backend,axiolid-mesh-compile0.3.31 andaxiolid-construct0.3.20, and, for the tests,axiolid-measure0.3.11, whoseexact_propertiesnow sums about a local point rather than the world origin (axiolid/kernel#343). - #525: advanced B-reps whose
IfcEdgeCurvecovers part of anIfcBSplineCurveWithKnotsorIfcPolylinecompile: the kernel trims a B-spline edge curve at its vertices by certified projection and a polyline exactly (axiolid/kernel#333), where 0.3.29 refused both by name. A vertex off its curve is refused by name ("not on its curve"). - #528: an advanced B-rep whose planar and curved faces share a straight edge, such as a D-shaped half cylinder, closes: every face takes the same samples of a shared edge (axiolid/kernel#336). It compiled open.
- A faceted B-rep face without a surface whose corners are not exactly coplanar now reports its slab width as its bound, not
0;0means planar. No test of ours pinned that0. - Tests: the split-rim cylinder's seam as a quadratic B-spline, a linear B-spline and polylines that run past both vertices (straight, with a collinear corner between them, and turning at the vertices with tails off the cylinder), along and against their curve, IFC4 and IFC4X3: watertight at
pi r^2 h, within the cylinder's extent, certified, the wall's measured deviation within the bound and the request. Seams trimmed to one straight span meet the 1 mm request (bound9.0e-4m); the quadratic B-spline and the polyline with a corner between the vertices are bounded piecewise and certified at1.59e-3m, above the request, pinned as the kernel's answer. B-spline and polyline seams a centimetre off their vertices are refused by name, replacing theTODO(#525)refusal pin. A D-shaped half cylinder (IFC4, IFC4X3) is watertight atpi r^2 h / 2, certified within the request (bound4.5e-4m). All three tests fail against 0.3.29.
[0.30.0] - 2026-10-20
ifc-geometry
Changed (axiolid-mesh-compile 0.3.29)
Semver: patch. No code change.
- Requires, behind
compile-reference-backend,axiolid-mesh-compile0.3.29, a kernel packaging fix: it bringsaxiolid-reference0.3.9, which requires theaxiolid-evaluate^0.3.17 it is built with (0.3.8 claimed ^0.3.12, so a resolver could pair it with an olderaxiolid-evaluate).
Added (#512)
Semver: minor. Every item below is new; compile_product_polylines and compile_product_mesh_for are unchanged.
compile::compile_product_curves/compile_product_curves_with: the curves of a product's representation ascompile::ProductPolylines,polylinesandskipped. EachIfcTextLiteral,IfcTextLiteralWithExtentandIfcAnnotationFillAreaamong the representation's ownItemsis not lowered but listed as acompile::SkippedItem(the item and itscompile::NonCurveItemkind,TextLiteralorAnnotationFillArea), so a dimension (its lines asIfcPolylines, its value as text) gives its lines instead of being refused. Nothing is drawn in a skipped item's place: a fill area's boundaries are not returned. Every other item lowers as before and its refusal still refuses the product; a text literal nested in a mapped representation is lowered, so refused, not skipped.compile_product_polylineskeeps the strict rule and refuses such a representation as a whole.
openbim-ifc
Changed (#433)
- Requires
ifc-geometrywith #433: anIfcOffsetCurveByDistancesalong a bounded basis whose length is a quadrature lowers, continued to the basis's ends the kernel measures, andproduct_graphslabels such a payload wire format 1.3 (axiolid-model0.3.12, axiolid/kernel#303). Every other payload keeps its version and bytes.
Semver: minor, following the dependency.
[0.29.0] - 2026-10-18
ifc-geometry
Changed (#506, #509: axiolid-mesh-compile 0.3.28)
Semver: minor. GeometryError::PolylineUnsupported and compile::UnpolylinedCurve are removed.
- Requires, behind
compile-reference-backend,axiolid-mesh-compile0.3.28 (which bringsaxiolid-brep-boolean0.1.11), and, for the tests,axiolid-evaluate0.3.17. - #506:
compile_product_polylinesreturns certified polylines for trimmed, composite, offset and intrinsic curves, and for open profiles over them (axiolid/kernel#317): composite joints are shared vertices, intrinsic-curve vertices certified positions. The reference compiler still refuses by name implicit and banked curves, 3D intrinsic curves with torsion, trims of relations or of placed curves, offsets of offsets, of placed curves or of chains, offsets across a corner of their basis, 3D offsets without a reference direction and offsets by stations. Those refusals, like every other, areGeometryError::CompilationRefusedcarrying the kernel's reason (for example "an offset of an offset curve"), naming the product.PolylineUnsupportednamed the top-level curve's family whatever the kernel refused, so it would now misname a refusal inside a composite or invalid data (a composite whose segments do not meet) as unsupported; it is removed withUnpolylinedCurve, and theTODO(#506)with them. - #509:
IfcEdgeCurvelowering stores an edge whoseSameSenseis.F.fromEdgeEndtoEdgeStart, used reversed. The kernel now trims a line, circle or ellipse edge curve without a p-curve at its vertices, taking the edge fromEdge::starttoEdge::endin the curve's own sense (axiolid/kernel#319); keeping the file's vertex order with only a reversed use selected the other arc of a closed curve. AnIfcSubedgeof such an edge is stored the same way round. Advanced B-reps whose edges cover part of their curve compile: anIfcLineseam with a unit vector, and half-circle rim edges along and against their circles. B-spline and polyline edge curves that do not run vertex to vertex are still refused by the kernel by name (#525, axiolid/kernel#333). - The #443 ring deck's frame law along an ellipse's offset is now certified (axiolid/kernel#316); its deviation stays unbounded at the mitre its widening law makes at station 2 (
MITRED_SECTIONS, axiolid/kernel#335). Its mesh and volume are unchanged. - Tests: the unit-vector seam cylinder (IFC4, IFC4X3) compiles, certified; a cylinder with split rims compiles to
pi r^2 hwith its half-circle edges along and against their circles in four combinations (IFC4, IFC4X3; mutation-checked against the old lowering); B-spline and polyline seams past their vertex are refused by name (TODO(#525)); the lowering of a falseSameSenseon an edge and a subedge; a trimmed circle axis in both senses, an offset of it, and a closed composite footprint with a trimmed arc (IFC2X3, IFC4, IFC4X3), and an IFC4X3 alignment footprint and spiral gallery, all against closed forms; an offset of an offset refused by name.
openbim-ifc
Changed (#429)
- Requires
ifc-geometrywith #429: sectioned solids and surfaces along offsets whose length is a quadrature lower with seam-snapping windows on their sections, andproduct_graphslabels those payloads wire format 1.2 (axiolid-model0.3.11, axiolid/kernel#301). Every other payload keeps its version and bytes: 1.0, or 1.1 when a station carries a window.
Changed (#427)
- Requires
ifc-geometrywith #427: with an evaluator, a linear placement on an offset whose length is a quadrature (a kerb beside an alignment, an offset beside a clothoid) derives, reading a joint within the model's precision from the incoming piece.
Semver: minor, following the dependency.
[0.28.1] - 2026-10-12
openbim-ifc
Fixed (axiolid/kernel#297)
- Requires
axiolid-model0.3.10 (throughifc-geometry'swirefeature):product_graphspayloads carry the lowest wire format version their content needs, 1.0 unless a station carries a seam-snapping window, then 1.1. Window-free payloads are labelled 1.0 again, readable byaxiolid-model0.3.8.
Semver: patch.
[0.28.0] - 2026-10-16
ifc-geometry
Changed (#515, #519: polygonal bounded half-spaces along Position's Z)
Semver: minor. axiolid-model 0.3.14 is a normal dependency whose types (GeometryGraph, SolidOperation) are exposed, and every lowered IfcPolygonalBoundedHalfSpace changes node; the three IFCPOLYGONALBOUNDEDHALFSPACE rows leave dispatch::PARTIAL.
- Requires
axiolid-model0.3.14 (axiolid/kernel#321, #322) and, behindcompile-reference-backend,axiolid-mesh-compile0.3.27 andaxiolid-construct0.3.19 (axiolid/kernel#321, #322, #325), which bringsaxiolid-evaluate0.3.16: offsets beside a 3D intrinsic curve without torsion in a level plane are now certified (axiolid/kernel#302), andcompile::window's repeatedUNCERTIFIED_LENGTHtext follows it. - Every
IfcPolygonalBoundedHalfSpacelowers toSolidOperation::BoundedHalfSpaceAlongZ: the half space within the boundary's prism alongPosition's Z, infinite both ways,Positioncarried whole (its scale, shear and handedness under a mapped item). That is the IFC set for every frame, so one path replaces three: aligned clips no longer lower toBoundedHalfSpace, a normal againstPosition's Z is no longer re-read from its other side, and the 0.27.2 chain of unbounded half spaces for oblique or rescaled prisms (lower_prism_clip,prism_walls, the boolean hook) and its frame classification are deleted. - Now compiled, where 0.27.2 refused them with
GeometryError::Unsupported: a non-convex boundary or one with a circular arc under an oblique or rescaled prism. Lowering refuses no frame any more; what the kernel cannot compile it refuses by name (GeometryError::CompilationRefused): aPositionZ lying in the plane, and the tool as a union's operand, a first operand or a root. - The #515 gable wall (
synthetic-compile/gable_wall_polygonal_clip_*.ifc) compiles exactly, at 49.999912042537 m³, and its mesh is certified against that body (bound about 4e-12 m); in 0.27.2 the mesh had no exact result to be measured against. - #519: both compilers size the cut from the subject (axiolid/kernel#325), so
synthetic-compile/tall_wall_small_bounded_clip_ifc2x3.ifcmeshes to the analytic 56.887388852960 m³, certified; the tests accept nothing else, and theirTODO(#519)is resolved. - Wire format: a lowered graph holding a polygonal bounded clip is Axiolid wire format 1.4 (it was 1.0); every other graph keeps its version.
- Tests: the gable (IFC2X3 and IFC4, gross, net and exact, certified), an L-shaped and an arc boundary under an oblique prism (analytic volumes, exact and certified), uniformly and unevenly scaled and mirrored mapped clips, and the named refusals of a Z in the plane and of a union.
openbim-ifc
Changed
- Requires
ifc-geometry0.22: stations on offsets whose length is a quadrature lower with Axiolid's seam-snapping window (#423), andproduct_graphsencodes withaxiolid-model0.3.9, wire format 1.1 (ifc::geometry::wire::FORMAT_VERSION). An envelope carries the lowest wire version its content needs: 1.0, or 1.1 when a station carries a seam-snapping window;axiolid-model0.3.9 labels every payload 1.1 (axiolid/kernel#297), and a reader onaxiolid-model0.3.8 or older refuses 1.1.
Semver: minor, following the dependency.
[0.27.2] - 2026-10-16
ifc-geometry
Fixed (#515, oblique polygonal bounded half-space clips)
Semver: patch. No public item changes; two IFCPOLYGONALBOUNDEDHALFSPACE rows of dispatch::PARTIAL are rewritten (an oblique or rescaled prism is now admitted for a convex boundary). Only graphs that were refused before change.
- A prism oblique to its plane, or under a scaled or sheared
Position, now lowers when the half space is the second operand of a difference or an intersection and its boundary is a convex polygon. The prism of a convex polygon is the intersection of one half space per edge (the plane through the edge alongPosition's Z), so the subject's intersection with the tool is a chain of intersections with unbounded half spaces, and a difference is the subject minus that chain. These are identities of sets; nothing is computed in the bridge (ADR 0004). The #515 gable wall (synthetic-compile/gable_wall_polygonal_clip_ifc2x3.ifcand_ifc4.ifc) compiles to 49.999912042537 m³, IfcOpenShell 0.8.5's volume, gross and net. - Limits, from axiolid-mesh-compile 0.3.26: the exact compiler takes no boolean as a difference's tool, so such a mesh's deviation report is unbounded and names the missing exact result; and a prism that misses its subject is refused by the mesh compiler (
CompilationRefused), not kept as the subject. - Still
GeometryError::Unsupportednaming the half space: a non-convex boundary, a boundary with an arc, a union with the tool, and the tool anywhere but a boolean's second operand. A kernel bounded half-space whose prism runs along its own placement (axiolid/kernel#321) is what covers these.
[0.27.1] - 2026-10-15
ifc-geometry
Fixed (#515, polygonal bounded half-space clips that compiled to another tool)
Semver: patch. No public item changes; dispatch::PARTIAL gains three IFCPOLYGONALBOUNDEDHALFSPACE rows. Two lowered graphs change, both of which compiled to a wrong mesh before, with no error.
- IFC bounds
IfcPolygonalBoundedHalfSpaceby the boundary's prism alongPosition's Z axis. Axiolid'sBoundedHalfSpacesweeps the boundary along the clip plane's normal and frames it byPosition's X projected into the plane andnormal x X. Lowering now checks that the two agree before emitting the node (lower::halfspace,prism_orientation). - A base plane whose normal points against
Position's Z (the usual "remove what is above" clip) mirrored the footprint acrossPosition's X axis. It now lowers as the same half space read from the other side, normal reversed and side flipped, so the footprint is the authored one. - A prism oblique to its plane (a sloped roof plane over a vertical prism: the usual IFC2X3 gable-wall clip) has no neutral equivalent. The boundary was laid into the slope and mirrored off the wall, so
compile_product_meshandcompile_product_mesh_netreturned the uncut body. It is nowGeometryError::Unsupportednaming the half space, until the kernel has a bounded half-space whose prism runs along its own placement. A scaled or shearedPositionframe, whose boundary the kernel would not scale, is refused the same way. - New fixtures
synthetic-compile/gable_wall_polygonal_clip_ifc2x3.ifcand_ifc4.ifc(the #515 wall), andtests/bounded_halfspace_prism_axis.rs, which accepts the gable's analytic volume or a refusal naming a clip, and never the uncut body.
[0.27.0] - 2026-10-14
ifc-geometry
Added (#458, curves and surfaces as output primitives)
Semver: minor. Every item below is new; RepresentationPurpose and GeometryError are #[non_exhaustive], so their new variants are additive. One refusal text changes (below); no lowered graph changes.
RepresentationPurpose::{Axis, FootPrint, Annotation, Surface}: each selects the first representation the product authors with thatRepresentationIdentifier, in authored order, whatever its context.BodyandPlankeep their policies.RepresentationPurpose::identifier,nameandfrom_namemap a purpose to and from the IFC spelling.compile::compile_product_polylines(_with): the curves of a product's representation for a purpose asCompiledPolylines (the IFCcurve, worldpoints,closed, and the backend'sChordDeviation: the chord budget asked for and a certified bound on the distance from the exact curve to the polyline). It takes anyPolylineCompiler, the reference backend's new operation (axiolid/kernel#311). A collection is opened, and a mapped item holding a curve places each curve on its own by the same composed transform (an exact regrouping; nothing is evaluated).compile::compile_product_mesh_for(_with): the surfaces and solids of a product's representation for a purpose as oneCompiledMeshwith itsMeshClosure. A representation with no curve compiles from its own root, so a Body's mesh is the onecompile_product_mesh_reportedgives. Rectangular trimmed surfaces and B-reps of loose faces now compile asMeshClosure::Surface(axiolid/kernel#311).GeometryError::PolylineUnsupported { product, curve, family, reason }withcompile::UnpolylinedCurve::{Trimmed, Composite, Offset, Intrinsic}: a trimmed, composite, offset or intrinsic curve the backend refuses to polyline is refused by that name,is_unsupported(), never approximated here. The reference compiler refuses them until axiolid/kernel#317 (#506).compilere-exportsChordDeviation,MeshClosure,Point3andPolylineCompiler, so a caller names noaxiolid-*crate.- Tests: an
IfcPolylineaxis, a placedIfcCirclefootprint far from the origin, a mapped axis, an annotation curve set, a rectangular trimmed surface, a loneIfcFaceSurface, a representation mixing both, and the trimmed and composite refusals, each in IFC2X3, IFC4 and IFC4X3.
Changed (#458)
- Uses the
PolylineCompilerofaxiolid-mesh-compile0.3.26 andaxiolid-mesh-compile-contract0.3.4, the floors #454 raised. GeometryError::CompilationRefusedreads "could not be compiled: ..." instead of "could not be compiled to a mesh: ...": it is now also the polyline compiler's refusal.
Changed (#454, curved advanced B-rep faces bounded by 3D edge curves compile)
Semver: patch. No public item, refusal kind or lowered graph changes. Only the dependency floors of the opt-in compile feature rise, as in 0.26.1, and bodies the reference kernel used to refuse now compile.
- Requires
axiolid-mesh-compile0.3.26 andaxiolid-mesh-compile-contract0.3.4. - An
IfcAdvancedBrepwhose curved faces are bounded only by 3D edge curves now compiles through the reference kernel. That is how IFC writes them:IfcAdvancedFaceadmits noIfcPcurveas edge geometry. Before, the kernel refused such a face. Now it inverts each edge's shared 3D samples onto the face's surface (axiolid/kernel#310). The lowering is unchanged and still projects nothing (ADR 0004). A seamed cylinder with two circular caps compiles as a watertight solid. So does a B-spline face bounded by B-spline edges over planar walls. - A curved face's certified deviation may come out slightly above the requested chord budget. The kernel adds the face's triangle bound to its boundary terms, so check
DeviationReport::meets_requested. The B-spline fixture reports 1.075 mm at a 1 mm request. - The kernel refuses some 3D-bounded faces, and each refusal reaches the caller as
GeometryError::CompilationRefusedwith the kernel's reason:- an edge through a sphere pole (or a cone apex);
- an edge curve whose whole domain does not run from its start vertex to its end vertex. This includes an
IfcLinewith a unitIfcVector, or a part of anIfcCircle: without a p-curve the kernel reads the curve's whole domain as the edge; - an edge off its B-spline surface. The kernel's certified inversion gives up on it as
BudgetExceeded("certified surface projection").
- The same kernel release bounds instanced bodies with the rounding of their placement. A chord budget at or below that rounding, about
1e-9at6e6, is refused (axiolid/kernel#295).
openbim-ifc
Added (#367, geometry Level 2, ADR 0021)
- Feature
geometry-wire(geometryplusifc-geometry/wire, infull):product_graphs(model, ids)->(EntityId, GeometryResult<ProductGraph>)per product,ProductGraph { world, graph }: each product's Body lowered into Axiolid's neutralGeometryGraphexactly asproduct_mesheslowers it, in world coordinates (metres), with its world placement alongside andNonefor a product with no Body. Encode a graph in Axiolid's wire format 1.0 withGeometryGraph::to_json/to_cbor(ifc::geometry::wire).compiled_features()reportsgeometry-wire.
Semver: additive (a new opt-in feature), a minor release while 0.x, since the ifc-geometry floor rises.
[0.26.1] - 2026-10-14
ifc-geometry
Changed (#443, stations along offset runs compile through the reference kernel)
Semver: patch. No public item, refusal kind, refusal text or lowered graph changes. Only the dependency floors of the opt-in compile feature rise, as in 0.25.1, and stations the reference kernel used to refuse now resolve.
- Requires
axiolid-mesh-compile0.3.25 andaxiolid-mesh-boolean-boolmesh0.3.6 (andaxiolid-evaluate0.3.15 for the tests). - A windowed station along an
IfcOffsetCurveByDistancesthat lowers toCurveRelation::OffsetsAtStationsbeside an ellipse or a B-spline now resolves through the reference kernel (axiolid/kernel#307). The kernel now certifies an offset law that varies along such a basis. 0.25.0 lowered these stations (#444), but the kernel refused them.- axioval/engine's deck (an
IfcSectionedSolidHorizontalfrom 0 to 5 m along such a run beside an ellipse) now compiles with its 1e-5 m windows. Its mesh is bit for bit the one it compiles to with the windows set to zero: a closed solid of 9.99945 m3, against 2 m2 x 5 m = 10 m3 by Pappus. The kernel's deviation report for it stays unbounded: the frame law along an offset beside an ellipse has no certified derivative bounds yet (axiolid/kernel#316). This crate does not read that report. - Derived
IfcLinearPlacements 10 m and 30 m along such a run now derive and equal the kernel's resolved stations. Before, they were refused asPlacementSeamWindowRefusedwithUncertifiedLengthorCertificationBudgetExceeded.
- axioval/engine's deck (an
SeamWindowRefusal::UncertifiedLengthandCertificationBudgetExceededstay. Their docs no longer name the cases the kernel now certifies. A station just past a coarsely bounded B-spline piece now pins theCertificationBudgetExceededresource name.- The kernel text this crate repeats to recognise
UncertifiedLengthfollowsaxiolid-evaluate0.3.15, which no longer lists an offset by distances along an ellipse or a B-spline. - The same kernel release snaps a boolean's residues to the scale of the coordinates its result is placed at (axiolid/kernel#312). A cut placed far from the origin, as with georeferenced coordinates, no longer leaves a hairline skin.
[0.26.0] - 2026-10-13
ifc-geometry
Changed
- Requires
ifc-alignment0.9 (alignment writers now refuse malformed GlobalIds, #492). No change in lowering. Semver: minor, because the dependency's minor bump changes a public requirement.
openbim-ifc
Changed
- Requires
ifc-geometry0.20: linear placements on curve-relation bases derive through curve paths (#418), and stations lower on offset-curve bases (#414), on Axiolid's wave-3 line. Semver: minor, following the dependency.
[0.25.1] - 2026-10-13
ifc-geometry
Changed (#439, stations on B-splines trimmed by points resolve in the kernel)
Semver: patch. No public item or lowering changes; only the dependency floors of the opt-in compile feature rise. Every product (1230) and every solid, surface and sweep item (2179) of the committed fixtures and references/competitor compiles to the same mesh, bit for bit, or to the same refusal under the old and the new releases.
- Requires
axiolid-mesh-compile0.3.24,axiolid-construct0.3.18 andaxiolid-mesh-boolean-boolmesh0.3.5 (andaxiolid-evaluate0.3.14 for the tests). - A station on an
IfcTrimmedCurveover anIfcBSplineCurveWithKnotstrimmed by Cartesian points now resolves through the reference kernel (axiolid/kernel#306). Before, the kernel refused it: its point inversion had no B-spline arm. It now uses the same inversion asPathBound::Point. A derivedIfcLinearPlacementon such a trim is the lowered station's frame, bit for bit, so lowering and derivation agree (#436 already derived it).- A trim point off the B-spline, or one it passes through twice, is refused by the kernel as
PATH_POINT_OFF_CURVEorPATH_POINT_AMBIGUOUS. These are the names the derivation reports asGeometryError::PathPointOffCurveandPathPointAmbiguous. - The kernel still refuses a polyline trimmed by points as a station basis (an upstream follow-up). No committed fixture uses one.
- A trim point off the B-spline, or one it passes through twice, is refused by the kernel as
- The kernel's deviation report for runs of sections at stations (
IfcSectionedSolidHorizontal,IfcSectionedSurface) isCertifiedwhere the frame law along the basis is bounded (axiolid/kernel#247). This crate does not read that report. Of the eight such items that compile in the committed fixtures, one now reportsCertified; the other seven stayUnboundedand now name their numerically framed basis. Their meshes are unchanged, as are the three refusals. - Swept disks are mitred at corners beside arcs, and closed polylines sweep as loops (axiolid/kernel#248). No committed fixture was refused for either before.
[0.25.0] - 2026-10-13
ifc-geometry
Changed (#443, stations along offset runs continued to their basis's ends)
Semver: minor (0.x). Stations that were refused now lower, a refusal kind is added to the #[non_exhaustive] SeamWindowRefusal, and refusal texts change. No graph that lowered before changes: every representation item of the committed fixtures and references/competitor (6851 items) lowers to byte-identical wire JSON or to the same refusal.
A station whose basis is an
IfcOffsetCurveByDistanceslowered toCurveRelation::OffsetsAtStations(#429, #433) now lowers. Its stations carry the kernel's seam-snapping window. Examples are anIfcSectionedSolidHorizontalorIfcSectionedSurfacesection, anIfcPointByDistanceExpression, or anIfcAxis2PlacementLinearon such a curve. Before, these were refused with "the basis curve relation is a parameter-space curve, a surface curve whose p-curve governs, or an instanced curve".- The relation is flattened as
axiolid-mesh-compile0.3.23 flattens it as a station basis. A station at the basis's start stands at0. - The span to a station at the basis's end runs open to the end the evaluator measures (#427).
- A windowed station stands at its own distance where no seam of the basis lies within its window.
- A derived
IfcLinearPlacementon such a curve reads the same path as the compiler, bit for bit.
- The relation is flattened as
An offset of an offset (
IfcOffsetCurveByDistances,IfcOffsetCurve2DorIfcOffsetCurve3Dover another offset curve, anOffsetsAtStationsincluded) is refused as a station basis by its own name, as Axiolid refuses it (axiolid/kernel#289). The text names that rule. A trim of anOffsetsAtStationsis refused as a trim of an offset.No offset relation reaches the catch-all refusal any more. Its text now names the offset curves Axiolid reads. A basis nested deeper than 64 relations is refused by its own reason.
With
compile,SeamWindowRefusal::CertificationBudgetExceededis new. It is returned where the kernel cannot certify a seam to a thousandth of the window within its panel budget (aBudgetExceededof "certified length panels"). It is reported, likeUncertifiedLength, asis_unsupported.Known limit, upstream (
TODO(#443)):axiolid-evaluate0.3.13 does not certify an offset by distances whose law varies along an ellipse or a B-spline. So a windowed station along such a run, past its first stated offset, is refused by the reference kernel:- for a sectioned solid,
RunSeamWindowRefused { refusal: UncertifiedLength }, which names the runs; - for a derived placement,
PlacementSeamWindowRefusedwithUncertifiedLengthorCertificationBudgetExceeded.
The lowered graph itself is correct. With its windows set to zero, the axioval/engine deck compiles to the closed-form prism.
- for a sectioned solid,
openbim-ifc
Changed
- Requires
ifc-alignment0.8 andifc-geometry0.19: a Viennese cant bend about a held rail lowers to the banked curve (#364), and derived linear placements on a seam take the incoming tangent (#409), on Axiolid's wave-2 line. Semver: minor, following the dependencies.
[0.24.0] - 2026-10-13
ifc-geometry
Fixed
- Requires
axiolid-model0.3.13: geometry graphs written as wire JSON (featurewire) read back bit for bit. 0.3.12 and older parsed about a quarter of doubles one ulp off; payloads already written are unchanged and now read back exactly.
Changed (#436, derived placements on spans inside quadrature-measured curves)
- With
compile, a derivedIfcLinearPlacementon a trim of an ellipse or a B-spline at an interior parameter, at a point, or at an arc length in its sense from a parameter0where its measure starts (the CUBIC transition'sIfcPolynomialCurvesegment) now derives instead of being refused withPathPieceLengthUnstated. Such a trim reaches the evaluator as a boundedPathPiece(PathBound::ParameterorPathBound::Pointin the curve's own coordinates, a stated distance for the arc length; axiolid/kernel#304), which it resolves as the compiler resolves the trim: inside the path it reads bit for bit as the onestation::curve_pathflattens the lowered basis into, a closed conic's span running across its parameter seam as the compiler's periodic trim does. The derived frame equals the lowered station's, resolved by the reference kernel. A B-spline trimmed by points derives although the reference compiler, which inverts no point on a B-spline, refuses that station; the frame equals its station on the trim by the parameters the points name. - A span between distances along a relation (a trim of a composite by parameters) may now reach a piece whose length is a quadrature when it is the relation's last piece and every piece before it is stated: the span is stated, so a derived placement reads it and the station lowering snaps onto its joints exactly, with no window, where before it carried the kernel's seam-snapping window.
- New
GeometryErrorvariants (featurecompile):PathPointOffCurveandPathPointAmbiguousfor a trim point the evaluator refuses (PATH_POINT_OFF_CURVE,PATH_POINT_AMBIGUOUS), andCurvePathBoundUnsupportedfor an evaluator that reads no piece bounds (PATH_PIECE_BOUND_UNSUPPORTED), each naming the placement and its basis.PathPieceLengthUnstatednow names only what has neither an open nor a bounded form: a span between distances along a relation reaching a quadrature piece other than its last, an arc-length trim of an ellipse or a B-spline against its sense, a varying offset of such a piece, and a trim of a relation by a Cartesian point. - The joints of a path with an open or bounded piece are certified only near the placement (
CurveEvaluator::path_joint_distances_within, axiolid/kernel#305): every piece before the window, none after it. A piece the evaluator cannot certify after the placement no longer refuses it; one before it still does, asPlacementSeamWindowRefusedwithUncertifiedLength. The kernel's station resolution also certifies the piece holding the station, so a placement inside such a piece (5 m along a tiltedIfcOffsetCurve3D, formerly refused) now derives while its lowered station is refused. - The station lowering is otherwise unchanged: it hands the kernel the relation, never a path, and links no evaluator to resolve a bounded piece, so a basis holding one keeps the kernel's seam-snapping window.
- Requires
axiolid-curve0.3.9,axiolid-model0.3.12,axiolid-evaluate0.3.13,axiolid-curve-evaluate-contract0.3.7 andaxiolid-mesh-compile0.3.23.
Semver: minor. Refused placements now derive, error variants are added, one station lowering now snaps exactly, and the dependency floors rise.
Changed (#433, offset runs to a basis end with quadrature length)
- An
IfcOffsetCurveByDistanceswhose basis is bounded but has no stated length (an offset beside a gradient curve, a spiral, a B-spline or an ellipse, a relation with an ellipse or a B-spline piece, a B-spline or an ellipse itself) now lowers instead of being refused with the formerNO_QUADRATURE_LENGTHreason or "states no length". IFC4.3 ADD2 8.9.3.42.3 continues its first and last offsets unchanged to the basis's ends: the run isCurveRelation::OffsetsAtStationsending inRunStation::at_basis_endwith the last offsets and, where the first offset stops short of the start, starting withRunStation::at_basis_start(axiolid-model0.3.12, axiolid/kernel#303). The kernel resolves the end to the basis's length by its own measure. Stations along a basis whose seams are quadratures keep their #429 seam-snapping windows. Such a graph is wire format 1.3. Bases with a stated length keep their stated continuation and emit the identical graph: every representation item of the committed fixtures and the reference corpus lowers to the same wire bytes as before. SeamWindowRefusal::PastBasisEnd(featurecompile): the kernel refuses a run that states a station at or past the end it measures, reported asGeometryError::RunSeamWindowRefusednaming the run, whichProvenanceMap::seam_window_runsnow also lists for runs continued to such an end.- Offsets that stop short of an unbounded basis (a line) stay refused, now with a reason naming the unbounded basis.
- Requires
axiolid-model0.3.12,axiolid-curve0.3.9,axiolid-evaluate0.3.13,axiolid-curve-evaluate-contract0.3.7 andaxiolid-mesh-compile0.3.23.
Semver: minor. Refused offset runs now lower, a refusal kind is added, and the dependency floors rise.
openbim-ifc
Changed
- Requires
ifc-geometry0.18: stations on composite, trimmed and station-placed curve bases lower with the seam rule at joints (#346), and curve segments placed by IfcAxis2PlacementLinear lower to stations (#311, part). Semver: minor, following the dependency.
[0.23.0] - 2026-10-12
ifc-geometry
Changed (#429, runs of sections along bases with quadrature joints)
- An
IfcSectionedSolidHorizontalorIfcSectionedSurfacealong a directrix whose seams lie at arc-length integrals (an offset beside a gradient curve, a spiral, a B-spline or an ellipse, a relation with an ellipse or a B-spline piece, a B-spline corner knot) now lowers instead of being refused with the formerRUN_UNLOCATEDreason. Each section stays at its own distance and carries the model's precision as Axiolid's seam-snapping window, readingSeamSide::Incoming(SectionAtStation::with_seam_window/with_seam_side,axiolid-model0.3.11, axiolid/kernel#301). The kernel snaps it onto the seam and mitres there at half angle (8.8.3.35.1); such a graph is wire format 1.2. Runs along bases whose seams are stored data keep their exact snapping and emit the identical graph (wire 1.0), as checked on the committed and reference corpora. GeometryError::RunSeamWindowRefused { product, runs, stations, refusal }(featurecompile) names the run when the kernel refuses its windows: two seams in a window, a seam on its edge, a length it cannot certify, or the newSeamWindowRefusal::NotIncreasing(positions that no longer increase strictly once snapped).ProvenanceMap::seam_window_runslists the windowed runs.- An
IfcOffsetCurveByDistancesalong such a basis is still refused, now by a reason naming #433: IFC continues its offsets to the basis's end, whose distance is a quadrature this crate does not compute. Its stations are built asRunStations; with a window they would lower toCurveRelation::OffsetsAtStations, and without one they still lower toOffsetByStations, unchanged. - A reversal at a quadrature seam within a run is refused by the kernel's mitre, which this crate cannot see.
- Requires
axiolid-model0.3.11,axiolid-curve0.3.8,axiolid-evaluate0.3.12,axiolid-curve-evaluate-contract0.3.6,axiolid-reference0.3.8 andaxiolid-mesh-compile0.3.22.
Semver: minor. Refused runs now lower, an error variant and a refusal kind are added, and the dependency floors rise.
Changed (#427, derived placements on offsets whose length is a quadrature)
- With
compile, a derivedIfcLinearPlacementon an offset whose length is a quadrature now derives instead of being refused: anIfcOffsetCurveByDistancesbeside anIfcGradientCurve(a kerb beside an alignment), an offset beside a clothoid or another spiral, offsets of B-splines and ellipses, a variable offset of a circle, and a relation with a whole ellipse or B-spline piece. Such a piece reaches the evaluator as an open-endedPathPiece(PathPiece::whole, axiolid/kernel#298), its end resolved to the evaluator's own length, so the path reads bit for bit as the stated path the kernel flattens the same relation into. The offset's base is split at every seam of its curve the kernel splits it at (chain joins, curvature seams, every profile break), so the pieces are the kernel's.basis_curve_pathreturns such a path. - The joints after an open piece are located by the evaluator's
path_joint_distances, certified to a thousandth of the window the station lowering hands the kernel (the model's precision, capped at 1 mm, or rounding). ADistanceAlongwhose window holds exactly one joint (or a stated piece's seam after one) reads it from the incoming piece (IFC4.3 ADD2 8.9.3.48.3, #409), at the evaluator's own point of the joint; the result equals the lowered station's frame resolved by the reference kernel, andCachedPositionPolicy::Verifycompares a cache against it. A segment of a basis placed at a windowed station along such a basis is read the same way, within the station's own window. GeometryError::PathPieceLengthUnstatednow names only spans that start inside a curve whose measure is a quadrature (a trim of an ellipse or a B-spline at an interior parameter, by an arc length or a point, a span between stations along a relation with such a piece), which no open piece expresses. Its message no longer cites #427.- The same
axiolidfloors as #429 above; with featurewire,wire::FORMAT_VERSIONis 1.2. A derived placement writes no graph.
Added (#427)
- With
compile:GeometryError::CurvePathOpenEndUnsupported { placement, basis }, an evaluator that reads stated spans only refusing an open piece (OPEN_PATH_END_UNSUPPORTED);GeometryError::PathJointDistancesUnsupported { placement, basis }, an evaluator that does not certify joints (PATH_JOINT_DISTANCES_UNSUPPORTED); andGeometryError::PlacementSeamWindowRefused { placement, basis, distance, refusal }, the placement's window holding two seams, a seam straddling its edge, or a joint the evaluator cannot certify (SeamWindowRefusal::Ambiguous,Straddled,UncertifiedLength), as the kernel refuses the same station. Each is typed; nothing falls back to an estimate.
Semver: minor while 0.x: placements that were refused now derive, a refusal narrows and error variants are added.
openbim-ifc
Changed
- Requires
ifc-geometry0.17: polygonal half-space boundaries with circular-arc segments lower exactly (#398), on Axiolid's 0.3.16 line. Semver: minor, following the dependency.
[0.22.1] - 2026-10-12
ifc-geometry
Fixed (axiolid/kernel#297)
- The
wirefeature requiresaxiolid-model0.3.10, whose writer labels each payload with the lowest wire format version its content needs: 1.0 unless a station carries a seam-snapping window, then 1.1. Window-free payloads are labelled 1.0 again, byte-identical to whataxiolid-model0.3.8 writes and readable by it; 0.3.9 labelled every payload 1.1.
Semver: patch.
[0.22.0] - 2026-10-10
ifc-geometry
Changed (#423, stations on offsets whose length is a quadrature)
- A station (
IfcPointByDistanceExpression,IfcAxis2PlacementLinear, and anIfcCurveSegmentplaced at one) on a basis whose seams lie at arc-length integrals now lowers instead of being refused: an offset beside a gradient curve (a kerb beside an alignment), a spiral, a B-spline or an ellipse, a variable offset of a circle, a relation with an ellipse or a B-spline piece, and a B-spline with a corner knot. It is anOrientedCurveStationat its own distance, readingSeamSide::Incoming, with the model's precision (capped at 1 mm) as its seam-snapping window (axiolid-model0.3.9, axiolid/kernel#294): the kernel reads it on a seam within the window from the previous segment (IFC4.3 ADD2 8.9.3.48.3), at the seam's certified distance. A station on a basis whose seams are stored data keeps this crate's own exact snapping, so its graph is unchanged (no window, 1.0 content): a run's stations carry no window and need that snapping anyway. - A run of sections or offsets (
IfcSectionedSolidHorizontal,IfcSectionedSurface,IfcOffsetCurveByDistances) along a basis whose seams are quadratures is refused by name (it was refused before by the basis's reason). axiolid-modelfloor 0.3.9,axiolid-curve-evaluate-contract0.3.5,axiolid-mesh-compile0.3.20 (featurecompile-reference-backend); all additive. With featurewire,wire::FORMAT_VERSIONis 1.1. An envelope carries the lowest wire version its content needs: 1.0, or 1.1 when a station carries a seam-snapping window;axiolid-model0.3.9 labels every payload 1.1 (axiolid/kernel#297), and a reader onaxiolid-model0.3.8 or older refuses 1.1.
Added
ProvenanceMap::seam_windows: the stations lowered with a window.- With
compile:GeometryError::StationSeamWindowRefused { product, stations, refusal }andcompile::SeamWindowRefusal(Ambiguous,Straddled,UncertifiedLength): the kernel's refusal of a window (two seams in it, a seam whose certified position straddles its edge, a seam it cannot certify) names the stations, where it wasCompilationRefusednaming the product. - With
compile:GeometryError::PathPieceLengthUnstated { placement, basis }: a derivedIfcLinearPlacementon such a basis is refused by name, since the evaluator'sCurvePathstates each offset piece's span in its own length, which only an execution provider computes (#427, axiolid/kernel#298); it wasUnsupportedwith the station lowering's reason.
Semver: minor while 0.x: stations that were refused now lower, a refusal changes variant, and the dependency floors rise.
openbim-ifc
Changed
- Requires
ifc-geometry0.16: stations on seams lower with the incoming tangent and runs mitre across seams (#346, part 1), on Axiolid's 0.3.15 line; the backend comparison example (#31). Semver: minor, following the dependency.
[0.21.0] - 2026-10-10
ifc-geometry
Added (#367, the neutral graph as a value)
- Feature
wire(off by default):ifc_geometry::wire, a re-export ofaxiolid_model::wire(Axiolid's versioned wire format of aGeometryGraph, Axiolid ADR 0085:FORMAT_NAME,FORMAT_VERSION1.0,FormatVersion,WireError), and with itGeometryGraph::to_json,from_json,to_cborandfrom_cbor, throughaxiolid-model'sserdefeature. It encodes the graph lowering produces and computes nothing (ADR 0004).
Changed
axiolid-modelfloor 0.3.8 (axiolid/kernel#267), which bringsaxiolid-core0.3.2,axiolid-curve0.3.7,axiolid-mesh0.3.3,axiolid-primitive0.3.3,axiolid-profile0.3.2,axiolid-surface0.3.3,axiolid-topology0.3.2 andaxiolid-linear0.3.2; all additive.
Semver: additive (a new opt-in feature), a minor release while 0.x, since the dependency floors rise.
openbim-ifc
Changed
- Requires
ifc-geometry0.15: collinearIfcArcIndexsegments lower as straight segments everywhere (#396), and geometry where-rules follow the declared release, with IFC2X3WRnnnames and the release-only rules (#397, #400, #402). Semver: minor, following the dependency.
[0.20.0] - 2026-10-10
ifc-geometry
Semver: a minor release. Behaviour changes: a linear placement whose basis curve is a curve relation, refused by name until now, derives through a CurveEvaluator, and an evaluator without curve paths is refused there by a new GeometryError variant (the enum is #[non_exhaustive]); stations and derived placements along offset curves, refused until now, lower and derive; an IfcOffsetCurve2D lowers with a reference direction where it carried none, a change in the lowered graph. A public function is added. The dependency floors rise with the workspace.
Added
- A derived
IfcLinearPlacementon a curve-relation basis (#418): a plainIfcCompositeCurve, anIfcTrimmedCurve, or a composite ofIfcCurveSegments placed byIfcAxis2PlacementLinears. The basis is read as Axiolid's neutralCurvePath(axiolid/kernel#290), built from the stored relation the station lowering reads (#346), each segment's station framed through the caller's evaluator; the frame comes fromCurveEvaluator::path_frame_atoff a joint andpath_frame_at_on(.., SeamSide::Incoming)within precision of one, as #409 reads a seam. It equals the lowered station's frame on and off joints, andCachedPositionPolicy::Verifyaccepts a cache computed with the incoming segment and refuses one computed with the outgoing segment. AnIfcParameterValuealong such a basis and a composite whose pieces do not meet are refused by name. constraint::placement::derive::basis_curve_path(compilefeature): theCurvePatha derivation reads along a curve-relation basis. A test pins it equal to the pathaxiolid-mesh-compile'sstation::curve_pathbuilds from the lowered graph.GeometryError::CurvePathUnsupported(compilefeature) names the placement and its basis curve when a caller's evaluator does not implement curve paths and refuses them withCURVE_PATH_UNSUPPORTED. A frame read on one piece is never used in its place.is_unsupported()is true for it.- Stations on offset-curve bases (#414): an
IfcPointByDistanceExpression, anIfcAxis2PlacementLinear, and runs of sections and offsets whose basis curve is anIfcOffsetCurve2D, anIfcOffsetCurve3Dor anIfcOffsetCurveByDistances, nested in composites and placements too, lower onto Axiolid's offset station basis (axiolid/kernel#289, Axiolid ADR 0082 amendment 2026-10-10). A distance runs along the offset's OWN length, not its basis's. The offset's joints, one per seam of its basis (every polyline vertex, every joint of a composite) and per station of an offset by distances, are read from the stored relation as the compiler flattens it (lower::station::relation::offset): beside a line or a polyline edge a line, constant or linear between stations, of lengthsqrt((L + dg)^2 + dl^2 + dv^2); beside a circle, constantly in its plane, a circle of radiusr - a,(r - a) / rtimes the arc's length. A station within the model's precision of a joint stands on it readingSeamSide::Incoming, the previous segment (IFC4.3 ADD2 8.9.3.48.3). The joints and lengths are pinned to the kernel's own withinARC_LENGTH_TOLERANCE:station::seamswhere the kernel states them exactly, the composite's seams alongstation::curve_pathwhere it measures a polyline offset by quadrature. A derivedIfcLinearPlacementon an offset basis reads it as aCurvePathofPathCurve::Offsetpieces (#418's path builder), equal tostation::curve_path's and to the lowered station's frame on the kerb's corner. synthetic-lowering/station_offsets_ifc4x3.ifc: anIfcOffsetCurve2Dof a line and a tangent quarter circle (a joint whose sides meet, an arc of radius 9), of a quarter circle (radius 11, 5.5 pi m), anIfcOffsetCurveByDistancesof an L polyline vanishing at its corner, a section run and an offset run along offsets, and anIfcOffsetCurve2Dacross the L's corner; tests through the reference kernel on and 4 um past each joint.
Changed
- Requires
axiolid-curve0.3.6,axiolid-curve-evaluate-contract0.3.4 and, behindcompile,axiolid-mesh-compile0.3.19 (andaxiolid-evaluate0.3.10 for the tests): the releases that add curve paths (axiolid/kernel#290) and measure a station along an offset (axiolid/kernel#289). The lock also movesaxiolid-nurbs0.3.6,axiolid-brep-boolean0.1.9 andaxiolid-overlay0.3.12. Every other test passes unchanged against them. - A closed conic trimmed across its parameter seam inside a relation basis is located from its start parameter taken modulo one turn, as Axiolid locates it, so its span starts within the curve's measure.
- The refusal of a basis curve the derivation does not read names the composite and trimmed curves among those it does.
- An
IfcOffsetCurve2Dlowers toCurveRelation::Offsetwith its frame's+Zasreference_direction, where it carriedNone(#414). Every curve lowers to a 3D curve, a 2D one inz = 0of its frame, and IFC's positiveDistance"in the sense of an anti-clockwise rotation through 90 degrees from the tangent vector T" (8.9.3.40) isZ x Tthere, the 3D offset'sV x T(8.9.3.41) and Axiolid's left lateral: the same curve. Axiolid refuses a planar offset law on a 3D curve as a station basis, soNoneleft nothing measurable along it. Nothing meshed an offset relation outside stations, so no compiled result changes. - The refusals of offset bases now name what the kernel refuses: an offset of an offset, a trim of an offset curve (it takes its basis's parameterisation, not its own length), an offset through a circle's centre, and an offset whose length is a quadrature (beside an ellipse, a B-spline, a spiral or a gradient curve, or a variable offset of a circle). An offset across a corner of its basis, whose sides do not meet (IFC: the basis "shall have a well-defined tangent direction at every point"), and a 3D offset whose tangent runs along its reference direction lower, and the kernel refuses them by name, typed. The dispatch ledger's
IfcPointByDistanceExpressionrows say so.
openbim-ifc
Changed
- Requires
ifc-geometry0.14:IfcPolygonalBoundedHalfSpaceboundaries given asIfcCompositeCurveorIfcIndexedPolyCurvewith straight edges lower (#393). Semver: minor, following the dependency.
[0.19.0] - 2026-10-09
ifc-geometry
Semver: a minor release. Behaviour changes on seams: a linear placement derived through a CurveEvaluator on a tangent discontinuity takes the incoming tangent, and an evaluator that cannot read that side is now refused there. GeometryError gains a variant (the enum is #[non_exhaustive]). The dependency floors rise with the workspace (#364, below); the seam sides are axiolid/kernel#286 in the same releases.
Changed
- Requires
axiolid-curve0.3.5,axiolid-curve-evaluate-contract0.3.3 and, behindcompile,axiolid-mesh-compile0.3.17 (andaxiolid-evaluate0.3.9 for the tests): the releases that rotate a banked curve about a held rail (axiolid/kernel#279), whichifc-alignmentnow lowers a Viennese bend onto. Nothing here builds a banked curve; every test passes unchanged against them. - A derived
IfcLinearPlacementon a tangent discontinuity takes the incoming tangent (#409), as IFC4.3 ADD2 8.9.3.48.3 asks ("the tangent of the previous segment governs") and as the lowered station has since #346.derive_placement_transformlocates the seams aslower::stationdoes, from the basis curve's stored data, snaps aDistanceAlongwithin the model's precision (capped at 1 mm) to the seam's distance, and asks the evaluator forCurveEvaluator::frame_at_on(.., SeamSide::Incoming)there (axiolid/kernel#286). A polyline parameter on a vertex is read at the vertex's distance. Off a seam nothing changes. Before, the derived frame on a grade break or a polyline corner was the outgoing one, so it disagreed with the same station lowered as geometry, andCachedPositionPolicy::Verifyrefused a cache an exporter computed with the incoming tangent while accepting one computed with the outgoing tangent; now the reverse holds.
Added
GeometryError::SeamSideUnsupported(compilefeature) names the placement, its basis curve and the seam's distance when a caller's evaluator does not implement seam sides and refusesIncomingwithSEAM_SIDE_UNSUPPORTED. The outgoing frame is never used in its place.is_unsupported()is true for it.
Documentation
- The derived frame's documentation states the evaluator's axes as kernel#242 documents them (x tangent, y up, z right); the derivation reads only the tangent, and a test pins the IFC frame
(tangent, left, up)to(x, -z, y)on and off a seam.
openbim-ifc
Changed
- Requires
ifc-geometry0.13: net bodies (lower_product_net_with) are subtracted in the host's frame under one worldInstance, so flush openings in georeferenced models mesh (#388). Semver: minor, because the re-exported neutral graph changes shape.
[0.18.0] - 2026-10-09
ifc-geometry
Semver: a minor release. No public item is added, removed or changes signature, but behaviour changes: stations along curve relations (#346) and IfcCurveSegments placed by an IfcAxis2PlacementLinear (#311), refused by name until now, lower. The Axiolid requirements are the ones #398 raised (axiolid-model 0.3.7, axiolid-mesh-compile 0.3.16).
Added
- Stations along curve relations (#346). Axiolid measures a station along a composite, a trim, a surface curve whose 3D curve governs and a curve placed at a station since
axiolid-model0.3.7 (axiolid/kernel#285, ADR 0082 amendment): end to end through the pieces, each in its own curve's convention, every joint a seam. So anIfcPointByDistanceExpression, anIfcAxis2PlacementLinearand the runs ofIfcSectionedSolidHorizontal,IfcSectionedSurfaceandIfcOffsetCurveByDistanceson a plainIfcCompositeCurve, anIfcTrimmedCurveor a composite of segments placed at stations lower. On a joint, and within the model's precision of one, a station stands on the joint readingSeamSide::Incoming, the previous segment, as IFC4.3 ADD2 8.9.3.48.3 asks. The joints are read from the stored relation (lower::station::relation, repeating the compiler's flattening, sinceaxiolid-evaluateis an execution provider this crate does not link): pieces' lengths from a line's direction, a circle's radius, a polyline's edges and the measure of a curve parameterised by it; trims by parameter, by arc length, or by a point on a line or a circle, a closed conic's within one turn; a segment whose sense disagrees reversed. The tests pin them against the kernel's own seams. - An
IfcCurveSegmentplaced by anIfcAxis2PlacementLinear(#311) lowers to anInstanceAtStation: the parent's piece in local coordinates (start at the origin, tangent alongx) placed in the frame of theOrientedCurveStationthe placement lowers to, whose localx,y,zare the station's tangent, left and up as IFC4.3 ADD2 8.9.3.4 reads them. A composite of such segments is measured in the segments' own arc length, whatever base they stand on. Part of #311:IfcSegmentedReferenceCurvestays refused (below). synthetic-lowering/station_relations_ifc4x3.ifc: a plain composite of a polyline, a polyline stored backwards (SameSensefalse) and a quarter circle trimmed by Cartesian points, with points on its joints and 4 um past one; a composite with a gap; and twoIfcLinesegments placed at stations 10 and 30 of a gradient curve. Tests resolve them through the reference kernel against closed forms.
Changed
IfcSegmentedReferenceCurve's refusal (dispatch::PLANNED) names what remains open in #311 precisely: its segments are curves in the (distance along, deviating elevation) space of its base curve, and no normative rule maps a segment'sParentCurveand placementAxisto a cant law and pivot; implementations differ in the coefficient scaling and the cross-slope formula.- An
IfcCurveSegmentin anIfcGradientCurveplaced by anIfcAxis2PlacementLinearkeeps its refusal, now naming why: a gradient curve's segments are read as plan and profile pieces, which need a resolved placement. - Uses the Axiolid releases #398 raised the workspace to (
axiolid-model0.3.7, axiolid/kernel#285 for stations along relations). Upstream now refuses a tilted placement of a plan-measured curve only, reading a tilted arc-length one in its own reference-up frame; nothing lowered before reached that rule, and no test's result changed with the bump alone.
Refused by name
- A station along an offset curve (
IfcOffsetCurve2D/3D), which Axiolid measures no station along; along a relation joining a gradient curve with arc-length pieces, through which no one distance runs; and along a relation with an ellipse or a B-spline piece, or a trim whose ends are not stated by its data, whose joints lie at an arc-length integral. A composite whose pieces do not meet, or whose undeclared sense runs a piece backwards, is refused by the kernel.
openbim-ifc
Added (#342, declared attribute types)
- Behind
schema-api:declared_type(schema, type_text)resolves an EXPRESS type token against a release's tables to aDeclaredType:Simple(SimpleType:INTEGER,REAL,NUMBER,STRING,BOOLEAN,LOGICAL,BINARY, widths dropped),Entity,Enumeration(with its items),Defined(with what it aliases, an aggregate included:IfcCompoundPlaneAngleMeasure),Select(every member resolved, nested SELECTs included),Aggregate, orUnresolvedfor a token the tables do not declare; bounded, so a cyclic alias endsUnresolved.attribute_type(schema, entity, name)gives an attribute's slot and declared type with its own aggregation levels outermost.
Semver: additive, a patch release.
[0.17.0] - 2026-10-09
ifc-geometry
Semver: a minor release. No public item is added, removed or changes signature, but behaviour changes as it did for #393: half-space boundaries with circular arcs, refused as Unsupported until now, lower, so a consumer matching on the boundary node now meets a Profile as well as a Curve2; PARTIAL loses the family's two rows; and the Axiolid requirements rise (axiolid-model 0.3.7, axiolid-evaluate 0.3.8, and with compile-reference-backend axiolid-mesh-compile 0.3.16 and axiolid-construct 0.3.17).
Added
IfcPolygonalBoundedHalfSpaceboundaries with circular-arc segments (#398): anIfcCompositeCurvewith trimmedIfcCirclesegments, or anIfcIndexedPolyCurvewith a non-collinearIfcArcIndex, lowers to aProfile::Contourboundary (no holes) whose arcs are exactCircle2segments, never chords.SolidOperation::BoundedHalfSpacetakes a profile boundary sinceaxiolid-model0.3.7 (axiolid/kernel#277, Axiolid ADR 0084): the reference exact compiler clips by a right circular cylinder per arc, and the mesh compiler flattens the arcs under its chord budget with a certified deviation. A boundary of straight edges still lowers to the same closedPolyline2as before, so its output is unchanged. Still refused, by name: asDegenerate, a gap at an arc's end (judged within the model'sPrecision), an open curve, a point off the plane and anIfcArcIndexwith coincident points; asUnsupported, a boundary circle placed by anIfcAxis2Placement3D, anIfcCurveSegmentmember, a reparametrised segment and any other curve family. A boundary that crosses or touches itself is the kernel's to refuse (InvalidInput, named alike in both compilers); it reaches the caller asCompilationRefused.IfcPolygonalBoundedHalfSpaceis now listed as implemented, with no variant rows.halfspace_boundaries_ifc4x3.ifcgains two walls, one clipped by a six-segment composite with two arcs of radius 1.2, one by an indexed boundary with anIfcArcIndex; each compiles, exact and meshed, to its closed-form volume.
openbim-ifc
Added (#328, geometry in the bindings, ADR 0021)
- Behind
geometry-select:product_placements(model, ids)-- per product with a shape (or per id given), its world placement (products_world_transforms, metres) and the Body representationselect_shape_representationchooses, asProductPlacement { product, world, body }, each half its ownGeometryResult-- withSelectedRepresentation(identifier, type, context) andSelectedContext(context type, derived from the parent for a sub-context; identifier; target view literal).column_major(&Transform)gives the 4x4 column-major matrix graphics APIs read. - Feature
mesh(geometryplusifc-geometry'scompile-reference-backend; never default, ADR 0004):product_meshes(model, ids), one result per product:ProductMesh { world, positions, indices },f32positions relative to the world placement (mapped back through its inverse inf64),u32indices, compiled with one reference backend at a one-millimetre tolerance; a product without a Body has empty arrays, a refused one itsGeometryError.compiled_features()reportsmesh.
Changed
fullnow includesmesh, and so links the reference compile backend.
Semver of #328: additive; mesh needs the compile re-exports of the next ifc-geometry (0.11.0).
[0.16.0] - 2026-10-09
ifc-geometry
Semver: a minor release (0.16.0). No public item is added, removed or changes signature, but behaviour changes (#346): stations on a tangent discontinuity and runs of sections and offsets across one, refused by name until now, lower (an IfcPointByDistanceExpression on a seam to an OrientedCurveStation, the only station that carries a seam side, where one off a seam stays a CurveStation); and the Axiolid requirements rise (axiolid-model 0.3.6, axiolid-curve 0.3.4, and with compile-reference-backend axiolid-mesh-compile 0.3.15, axiolid-mesh-compile-contract 0.3.3 and axiolid-construct 0.3.16). The example below and the compile module's new section alone would be a patch.
Added
examples/backend_compare/(#31;required-features = ["compile"]) compiles a fixed corpus of committed fixtures through every backend in one list, with nocfgin the traversal. The list holds the reference backend undercompile-reference-backend, andpolygon-extruder, aMeshCompilerdefined in the example that names only the published contracts and builds undercompilealone.polygon-extrudercompiles collections and instances, triangle and polygon meshes, faceted B-reps, extrusions of polygonal profiles with holes, and blocks, and refuses everything else withGeomError::UnsupportedInput. The example prints:- wall time per fixture, keyed by
BackendId: the first pass reported on its own, then the median and range of--iterationspasses; - mesh size (vertices, triangles) and coverage (meshed, refused, no body) per fixture;
- agreement on signed volume, area and bounding box within a stated relative tolerance, every divergence named by fixture, product, metric and both values;
- every product one backend meshed and the other refused.
The method, machine, profile and load are printed beside the numbers, which are not a cross-kernel performance claim.
scripts/gate.shruns it quick under both features and with--strictbeside the reference. Thecompilemodule docs and ADR 0012 (amendment) link it.- wall time per fixture, keyed by
Stations on tangent discontinuities (#346). IFC4.3 ADD2 8.9.3.48.3: "If DistanceAlong coincides with a point of tangential discontinuity (within precision limits), then the tangent of the previous segment governs." Axiolid now states a seam side (
SeamSide,axiolid-model0.3.6, ADR 0082 amendment, axiolid/kernel#263), so such a station lowers withSeamSide::Incoming: anIfcAxis2PlacementLinearas anOrientedCurveStationreading the incoming side, anIfcPointByDistanceExpressionasCurveStation::with_seam_side's unturned oriented station.OffsetLongitudinalthen runs along the incoming tangent.Runs across tangent discontinuities (#346):
IfcSectionedSolidHorizontal,IfcSectionedSurfaceandIfcOffsetCurveByDistanceswhose stations span a polyline corner or a grade break lower, and Axiolid cuts them in the half-angle mitre plane 8.8.3.35.1 and 8.8.3.37.1 ask for (the plane normal to the bisector of the two tangents). IFC states no join for the offset curve; the mitre is the one the sweeps on the same stations take.synthetic-lowering/station_seams_ifc4x3.ifc: a gradient curve with a grade break and an L-shaped polyline, with points on and within the precision of each seam, a sectioned solid and an offset curve (and a disk swept along it) across the corner, and a sectioned surface across the grade break. Tests resolve them throughaxiolid-referenceand the reference mesh compiler against closed forms: the incoming tangent, the mitre's corners, the volume and the area.
Changed
- A station within the model's declared
Precisionof a seam (capped at 1 mm, or1e-9 * max(1, |s|)where larger) is stored at the seam's own distance (#346). Axiolid reads a station on a seam only withinARC_LENGTH_TOLERANCE * max(1, s)(1e-12); snapping to the distance Axiolid itself reads from the same stored data (pinned againstexact_station_seamsin the tests) makes the two rules agree, where a station 4 um off a corner would otherwise be on the corner for IFC and on one leg for the kernel. A station of a run is snapped the same way, so its section stands in the mitre plane; two that snap onto one seam are refused as out of order. - The seam reader carries whether the curve turns back on itself at a seam, by Axiolid's
MITRE_TOLERANCE(1e-6 on the cosine of half the turn), repeated inlower::station::seamsbecauseaxiolid-evaluateis an execution provider this crate does not link (ADR 0004). - Workspace requirements:
axiolid-model0.3.6,axiolid-curve0.3.4,axiolid-evaluate0.3.7 (dev),axiolid-mesh-compile0.3.15,axiolid-mesh-compile-contract0.3.3 andaxiolid-construct0.3.16. The kernel now reads a station withinARC_LENGTH_TOLERANCEof a seam at the seam; no test's result changed with the bump.
Refused by name
- A run of sections or offsets across a seam where the basis turns back on itself: "very sharp edges may result in nearly impossible miter" (8.8.3.35.1), and the half-angle plane has no extent there.
- Still: a station on a basis whose seams cannot be read from stored data. A curve relation (a plain
IfcCompositeCurve, a trimmed or offset curve): Axiolid resolves no station along one, which remains open in #346. A B-spline with a corner knot, whose distance is an arc-length integral (Axiolid'sexact_station_seamsrefuses it too).
openbim-ifc
Changed
spatial_propertiesand the property-edit planner (apply_property_edits,stage_property_edits) resolve through oneifc_properties::PropertyIndexof the model instead of calling the exact resolver per element or per edited object, so listing every element's properties, or a batch editing many objects, is linear in the model (#352). Answers and refusals are unchanged.ifc::properties::PropertyIndexis reachable through thepropertiesfeature.- Behind
geometry, the re-exportedifc_geometrygains, undergeometry::lower,lower_product_net_withandNetOptions(#351): a Reference View opening with only aReferencerepresentation can be taken as already applied to its host's Body, listed inNetLowering::taken_as_applied. Withifc-geometry'scompilefeature it also gains the evaluator-taking linear placement (#353,LoweringSession::with_curve_evaluator,product_world_transform_with_evaluator) and the cached-position check (#354,CachedPositionPolicy), and the derived frame follows IFC4.3 (#355). Whenifc-geometry0.10 is released, the re-export moves to it, which makes the next release here a minor one.
Semver of #352 alone: no public signature changes, a patch release.
[0.15.0] - 2026-10-09
ifc-geometry
Semver: an IfcIndexedPolyCurve whose collinear IfcArcIndex was refused as Degenerate now lowers (#396), and rules::validate no longer reports BoundaryType for an IFC4X3 IfcIndexedPolyCurve boundary or a subtype of IfcCompositeCurve (#397). Every where-rule is now checked in the file's declared release and reported under that release's name (#400): an IFC2X3 file's violations are named WR1, WR31, ... as IFC2X3 TC1 names them, and the set of violations changes for IFC2X3 and IFC4X3 files as listed below. No public item is added, removed or changes signature (rules::validate, rules::validate_model, rules::placement::check, rules::solid::check and RuleViolation are as before); a consumer matching IFC2X3 violations by their IFC4 rule name must match the WRnn name instead. Behaviour changes only, so the next release is a minor one (0.15.0); nothing is breaking at the API level. No committed fixture's violation set changes. The where-rules of the geometry entities IFC4 does not declare (#402) add violations only, on files that break them, and public API is unchanged, so they join the same minor release.
Added
rules::validateandrules::validate_modelcheck the where-rules of the geometry entities IFC4 ADD2 TC1 does not declare, each in the releases that declare it, under that release's name and text (#402), and theDECLAREDtable lists them so the schema-backed tests hold them to each bundled release:- IFC2X3 TC1
Ifc2DCompositeCurve.WR1(ClosedCurve) andWR2(Dim = 2);IfcRationalBezierCurve.WR1(one weight per control point) andWR2(IfcCurveWeightsPositive); - IFC4X1 on:
IfcSectionedSolid.ConsistentProfileTypes,DirectrixIs3DandSectionsSameType;IfcSectionedSolidHorizontal.CorrespondingSectionPositionsandNoLongitudinalOffsets(IFC4X1 and IFC4X2 read the offset on theIfcDistanceExpressionposition, IFC4X3 ADD2 on the position'sIfcPointByDistanceExpressionlocation);IfcTriangulatedIrregularNetwork.NotClosed(a writtenClosed = TRUEviolates; an omitted one is UNKNOWN and conforms); - IFC4X3 ADD2:
IfcAxis2PlacementLinear.WR1(the location is anIfcPointByDistanceExpression) andWR2(Axis not parallel to RefDirection, read asAxisToRefDirPositionis);IfcPolynomialCurve.CorrectPositionDimandValidCoefficients;IfcSectionedSurface.AreaProfileTypes(at least oneCURVEcross-section, as its text demands),CorrespondingSectionPositions,DirectrixIs3D,NoOffsetsandSectionsSameType.
#402 listed the sectioned solids and the TIN as IFC4X3-only; IFC4X1 and IFC4X2 declare them too, and are checked.
IfcCurveWeightsPositiveis transcribed once (rules/express.rs) for both the Bezier curve and IFC4'sIfcRationalBSplineCurveWithKnots.WeightsGreaterZero: itsWeightsarray isIfcListToArray(...) = ?whenWeightsDataandControlPointsListdiffer in length, and the function then returns TRUE, so a rational B-spline with mismatched lists now reports onlySameNumOfWeightsAndPoints, not alsoWeightsGreaterZero.DimfollowsIfcPointDim(IFC4X3 ADD2) and theDimofIfcPointOnCurveandIfcPointOnSurface(every release): a point'sBasisCurveorBasisSurface, and anIfcPointByDistanceExpression'sBasisCurve, where it was undecided.- IFC2X3 TC1
Changed
- The general curve lowering (
lower_curve_node, and every representation item, axis or directrix that reaches it) treats a collinearIfcArcIndexas IFC4 ADD2 TC1 and IFC4X3 ADD2 prescribe: "The three points shall not be co-linear. In case that this informal proposition is not maintained, the arc segment shall be treated as a polyline segment" (#396). Three distinct points collinear within the model'sPrecisionlower as a straightCurve3::Polylinesegment of the composite, start -> end when the middle point lies between the others and start -> mid -> end when it does not; they were refused asDegenerate. Two points that coincide withinPrecisionare stillDegenerate. Both tests now use the model'sPrecision(in the curve's own coordinates,1.E-5project units when none is declared) instead of an absolutef64::EPSILONbound on a squared area, and they are one helper,constraint::tolerance::arc_points, shared with the profile and half-space boundary reader of #335 and #393, whose results are unchanged except that its tolerance, like every point comparison's, is floored at floating-point rounding for very large coordinates. A model whose declaredPrecisionis not a finite positive number now refuses an indexed curve with an arc segment, as it refuses an indexed profile boundary. The fixtureindexed_curve_arcs.ifc(IFC4) lowers representation curves with a collinear, an out-and-back collinear, a nearly collinear and a genuine arc, andhalfspace_boundaries_ifc4x3.ifcgains a wall whose boundary has a collinear arc; both pass the dispatch corpus.
Fixed
- The where-rule
IfcPolygonalBoundedHalfSpace.BoundaryTypeis checked in the file's own release (#397). IFC4X3 ADD2 admitsIfcPolyline,IfcCompositeCurveandIfcIndexedPolyCurve; IFC2X3 TC1 (WR42), IFC4 ADD2 TC1, IFC4X1 and IFC4X2 admit only the first two. An IFC4X3 file with an indexed boundary was flaggedWrongType. The release is the header'sFILE_SCHEMA, IFC4 ADD2 TC1 when none known is declared, and, asTYPEOFincludes supertypes, a subtype of an admitted type (anIfcBoundaryCurve) satisfies the rule, judged in that release's entity table. - Every where-rule in
rulesis checked against the declared release's own text (#400). One helper,rules/release.rs, reads the release fromFILE_SCHEMA(IFC4 ADD2 TC1 when none known is declared), lists which rules each bundled release declares on which entity, and answers everyIN TYPEOFtest in that release's entity table; no rule matches names by hand any more. Two tests hold it to the schemas: the rule list must equal each bundled release's own table, and each rule's EXPRESS text in every release (fromreferences/ifc-spec) must be IFC4's except where the code branches on and cites the difference.IfcBooleanClippingResult.FirstOperandType: IFC2X3 TC1 (WR1) admits only anIfcSweptAreaSolidorIfcBooleanClippingResult, so an IFC2X3 swept disk is now reported. IFC4 ADD2 TC1 to IFC4X3 ADD2 spell the third disjunctIFCSWEPTDISCSOLID, which names no entity; it is read asIfcSweptDiskSolid, as buildingSMART/IFC4.x-development#927 and its open correction #1107 state, so a clipped swept disk stays admitted.- Rules IFC2X3 TC1 does not declare are no longer applied to IFC2X3 files:
IfcBooleanResult.FirstOperandClosed/SecondOperandClosed,IfcDirection.MagnitudeGreaterZero,IfcRepresentationMap.ApplicableMappedRepr, andDirectrixBounded. IfcRevolvedAreaSolid.AxisStartInXYin IFC4X3 ADD2 also requiresAxis.Locationto be anIfcCartesianPoint; IFC4X3'sLocationIsCPonIfcAxis1Placement,IfcAxis2Placement2DandIfcAxis2Placement3Dis now checked too.DirectrixBoundedin IFC4X3 ADD2 is declared onIfcDirectrixCurveSweptAreaSolidand so binds every subtype, includingIfcDirectrixDerivedReferenceSweptAreaSolid, which was not checked.- IFC2X3 TC1's
IfcSweptSurface.WR1(noIfcDerivedProfileDefas the swept curve), dropped in IFC4, is checked for IFC2X3 files. - A violation names its rule as the declared release does (
WR1-WR5on an IFC2X3IfcAxis2Placement3D), and itstype_nameis the violating entity's own type (anIfcExtrudedAreaSolidTaperedwas reported asIFCEXTRUDEDAREASOLID). - Rules declared on a supertype now bind subtypes the name matching missed:
First/SecondOperandClosedreadClosedon an IFC4X1-onIfcTriangulatedIrregularNetwork, andApplicableMappedReprjudges the release's wholeIfcShapeModelfamily. Dimfollows each release'sIfcCurveDimand IFC4X3'sIfcSegmentDim: the IFC4X1-on and IFC4X3 curve families (IfcOffsetCurveByDistances,IfcGradientCurve,IfcPolynomialCurve,IfcSpiral, ...) and anIfcCurveSegment'sParentCurve(slot 4) resolve, so the dimensional rules reach IFC4X3 alignment geometry, andIfcGridAxis.WR1derivesAxisCurve.Dimthe same way. InIfcGetBasisSurface, an IFC4X3IfcCurveSegmentcontributes itsParentCurve's surface, where slot 2 was read and anIfcCompositeCurveOnSurfaceof curve segments was reported asSameSurface.- A build that leaves the declared release's table out answers subtype tests from the compiled geometry chains and does not report a rule that demands a type for an entity those chains cannot classify.
openbim-ifc
Changed (breaking)
- Behind
geometry, the re-exportedifc_geometryis 0.9 (was 0.8): IFC4X3 stations, linear placements, offset curves by distances and sectioned solids/surfaces lower exactly (#307), and anIfcParameterValuedistance on an alignment centreline is refused instead of being read as plan distance (#347).
Added (#330, checked authoring)
- Feature
authoring:ifc-authorwithout choosing a release (authornow meansauthoringplusschema, as before), and the checked authoring batch.apply_authoring(model, schema, ops, seed)runsAuthorOps in order against the model as the operations before them leave it and commits them as one transaction, or refuses withAuthoringError(the operation's position and anAuthoringFailure) and changes nothing;stage_authoringplans without committing. AuthorOp(#[non_exhaustive]):Createan entity by type and named attributes,Editnamed attributes,Removean entity with the relationships that reference it, and the buildersProject,Spatial(aggregated under its parent),Product(placed, contained and typed),TypeObject,AssignType,Contain,Aggregate,Placement(anIfcLocalPlacementover anIfcAxis2Placement3D) andOwnerHistory(throughifc-author's writers). Every record is built by name throughifc-author'sEntityBuilderagainst the declared release, then its references are resolved (type checked withSchema::accepts_type) and its aggregates held to their declared bounds; a suppliedGlobalIdanother entity holds is refused; an object is contained, aggregated or typed once, and a model holds oneIfcProject.- An
IfcRootcreated without aGlobalIdgets a name-based one over a seed (fresh_seed(), from the OS on native targets) and its id; a fixed seed reproduces a file.OwnerHistoryis never invented. authoring_handle(i)/HANDLE_BASE(2^62): an operation names the entity an earlier one produced;AuthoringOutcome::idsreports the ids.- Additive: a patch release. The property edit's name-based
GlobalIdmoved into a shared module unchanged (a test pins its output).
[0.14.0] - 2026-10-09
ifc-geometry
Semver: IfcPolygonalBoundedHalfSpace boundaries this crate refused as Unsupported now lower, and a malformed composite or indexed boundary is refused as Degenerate (an empty composite, a gap, an open curve, a point off the plane) instead of Unsupported. No public item is added or removed, and PARTIAL gains two rows; behaviour changes as it did for #335, so the next release is a minor one (0.14.0).
Added
IfcCompositeCurveandIfcIndexedPolyCurveasIfcPolygonalBoundedHalfSpace.PolygonalBoundary(#393).BoundaryTypeadmitsIfcCompositeCurvein IFC2X3, IFC4 ADD2 TC1 and IFC4X3 ADD2 andIfcIndexedPolyCurvein IFC4X3 ADD2; an IFC4 file using the latter lowers the same way, the curve being defined identically there, and the rule is left to validation. Both are read by the profile boundary readers (#43's composite walk, #335's indexed reading) under a half-space role, soSameSense, nested composites, trimmedIfcLinesegments, the collinear-arc fallback ("treated as a polyline segment") and the closure rules are shared, and the closed, straight-edged result becomes the oneCurve2::Polylinewithclosedset thatSolidOperation::BoundedHalfSpacetakes. Composite joints are judged within the model'sPrecision("the tolerance under which two given points are still assumed to be identical",1.E-5project units when none is declared), and a point keeps theIfcPolylineboundary's rule: 2D, or 3D withz == 0exactly, inPosition's XY plane (BoundaryDim). Refused, naming the entity: asDegenerate, a gap between segments, an open curve, an empty composite, a point off the plane and the indexed refusals of #335; asUnsupported, a circular arc (a trimmedIfcCircleor a non-collinearIfcArcIndex), because Axiolid'sBoundedHalfSpacetakes only aPolyline2(axiolid-mesh-compile0.3.14 refuses every otherCurve2, andCurve2has no composite variant) and an arc is never polygonised; an IFC4X3IfcCurveSegmentmember; and other segment parents. TheIfcPolylineboundary is unchanged.PARTIALlists the family's admitted and refused boundary forms. The fixturehalfspace_boundaries_ifc4x3.ifcclips a wall by each form beside itsIfcPolylinetwin; each lowers to the twin's polyline and compiles withcompile-reference-backendto its volume.
openbim-ifc
Changed (breaking)
- Behind
property-catalog, the re-exportedproperty_catalogisifc-template-catalog0.4 (was 0.3): one compact snapshot container replaces the per-edition bincode files, with newsnapshotandruntimemodules (#317). The newproperty-catalog-runtimefeature checks property writes against a catalog supplied at run time (#318).
Added (#326, attributes by name)
attribute_slots(schema, type_name)andattribute_slot(schema, type_name, name)(featureschema-api): an entity type's explicit attribute slots in Part 21 order, inherited first, asAttributeSlot(index, name, declared type,optional,aggregate,derived,declared_by).INVERSEand newDERIVEattributes hold no slot and are not listed; an inherited attribute a subtype redeclares as derived is markedderived. Names match ASCII case-insensitively, as EXPRESS identifiers do; answers use the schema's spelling.attribute_by_name(model, schema, id, name)andset_attribute_by_name(model, schema, id, name, value): read and write by name against the schema the caller passes. The write refuses a derived slot and checks everything before its one write.NamedAttributeError(#[non_exhaustive]):MissingEntity,UnknownEntity,UnknownAttribute,DerivedAttribute.- Additive: a patch release.
Added (#318, runtime PSD/QTO catalog)
- Feature
property-catalog-runtime: property edits checkPset_/Qto_sets against a catalog the host installs at runtime (property_catalog::runtime::install) instead of the embedded one; until the release's edition is installed such an edit is refused with the newPropertyEditFailure::CatalogNotLoaded { set, edition }and nothing is written. Withproperty-catalogtoo, the embedded catalog is used.property-catalognow enablesifc-template-catalog/embeddedexplicitly. The embedded catalog shrinks from 3.7 MB to 1.4 MB with the catalog's compact format (#317). - Additive (
PropertyEditFailureis#[non_exhaustive]): a patch release on its own.
[0.13.0] - 2026-10-09
ifc-geometry
Semver: no public item is added, removed or changed in signature, and the net geometry is the same up to floating-point rounding. But the neutral graph lower_product_net/lower_product_net_with return is shaped differently, and the module documentation described that shape: a caller that matches NetLowering::root or a Subtraction::result as a Boolean, or reads a Subtraction::body as the opening's placed solid, now finds an Instance above it. Hosts the kernel refused also mesh now. That is an observable behaviour change for code that inspects the graph, so the next release is a minor one (0.13.0), not a patch.
Changed
- Net lowering subtracts openings in the host's frame, not in world coordinates (#388). The host's Body is lowered without its world placement, each opening's Body by its placement relative to the host's, the
Differencenodes run in that frame, and oneInstancewith the host's world transform (contextWorldCoordinateSystemincluded, applied once) is put aboveNetLowering::gross, eachSubtraction::bodyand eachSubtraction::result; all of them stay in world coordinates. A host of several solids has each part cut in that frame. The relative placement is composed along theIfcLocalPlacementchain the opening and the host share, from their first common placement down, so a georeferenced site's translation and turn never enter it; a grid or linear placement may be that common placement or sit above it. When an opening shares no chain with its host (noPlacementRelTopath to a common placement, or a Body context whose frame differs from the host's), the net body is lowered in world coordinates exactly as before, rather than guessed.
Fixed
- A wall with openings flush with both its faces, under a site placed at survey coordinates (600 000, 5 600 000) and turned to grid north, meshes net with
compile-reference-backend(#388). In world coordinates the opening's faces were rounded off the wall's by up to an ulp of 5.6e6, andaxiolid-mesh-compile0.3.14 refused the result as touching itself along the opening's edges. The wall ofifclite-geometry/issue_098_wall_W.ifc, refused that way before, nets to 32.419 m³ now. Net volumes over the rest of the fixtures and the reference corpus are unchanged to 1e-8 m³.
openbim-ifc
Added (#123, property sets: write side)
apply_property_editsandstage_property_edits(featureproperties): a batch ofPropertyEdits (set or remove a value, addressed by object, set name and property name) planned against the model and staged on oneTransaction, committed all or nothing. An inherited value is overridden on the occurrence, never on the type's shared set; a shared set or property entity is copied before it changes; an emptied set is removed with its relationship. Values are checked against the declared release (IFC2X3, IFC4, IFC4X3) and, withproperty-catalog, aPset_/Qto_set against the release's PSD/QTO catalog; without it such a set is refused (PropertyEditFailure::CatalogUnavailable). New sets and relationships take the object's owner history and a name-basedGlobalId. TypesPropertyEdit,SetType,PropertyEditOutcome,StagedPropertyEdits,PropertyEditError,PropertyEditFailure.- Additive: on its own a patch release; this release is a minor one for #306 anyway.
Changed (breaking)
validate,spatial,geometry-select,properties,classification,material,systems,costandgeoreflink only the releases the build names (#306): each release feature (ifc2x3...ifc4x3, andschemafor all five) now forwards to their crates, which no longer pull every release's table in throughifc-schema's defaults. An IFC4-only build with all of them carries the IFC4 table alone. A build that enabled one of these features without a release feature must now add one (orschema):spatialandpropertiesfail to compile without one, and the others refuse every file with their unsupported- or unbundled-schema error.fullanddomains(which impliesschemathroughauthor) are unchanged. Under 0.x, a minor release.
Added
root_identity(model, schema, id)andRootIdentity(behindschema-api): theGlobalIdandNameof an entity the given schema declares anIfcRoot, read by attribute name, never guessed from a slot (#123).georeferencing(model)andGeoreferencingError(behindgeoref+properties): everyIfcCoordinateOperationresolved with the project length unit, whichifc-propertiesresolves exactly; the join the language bindings carry (#123).- Re-exports
Budgetfromifc-model, which bounded domain traversals (a classification hierarchy) take.
[0.12.0] - 2026-10-08
ifc-geometry
Semver: profile boundaries this crate refused as Unsupported now lower, a malformed IfcIndexedPolyCurve boundary is refused as Degenerate instead of Unsupported, and profile_outline refuses open indexed curves it used to close. No public item is added or removed, but behaviour changes, so the next release is a minor one (0.12.0).
Added
IfcIndexedPolyCurveas theOuterCurveandInnerCurvesofIfcArbitraryClosedProfileDefandIfcArbitraryProfileDefWithVoids(#335), per IFC4 ADD2 TC1 and IFC4X3 ADD2: anIfcLineIndexbecomes one straightLine2edge per consecutive index pair, anIfcArcIndexthe exactCircle2through its three points with an angle domain, and a curve withoutSegmentsone straight edge per consecutive point pair. Nothing is chorded. The contour is the one theIfcCompositeCurvereader builds for the same outline (a trimmedIfcCirclefor the arc), so both lower to the sameProfile. AnIfcArcIndexwhose three distinct points are collinear lowers as the polyline start -> mid -> end (one edge when the middle point lies between the others, two when not), as the schema says: "the arc segment shall be treated as a polyline segment". Coincidence and collinearity are judged within the model's declaredPrecision(1.E-5project units when none is declared), "after taking the Precision factor into account". Refused asGeometryError::Degenerate, naming the curve: an open curve (closure by index withSegments, by first and last point coinciding within thePrecisionwithout), segments that break WHERE ruleConsecutive, an arc with two coincident points,SelfIntersectTRUE, and a 3D point list. Other boundary curve families are stillUnsupported.
Changed
profile_outlinerefuses an openIfcIndexedPolyCurveboundary asGeometryError::Degenerate, as the profile lowering does (#335). It closed one implicitly before, reporting a ring whose closing edge the file never authored. Closure is the schema's: by index withSegments, by the first and last point coinciding within the model'sPrecisionwithout.IfcPolylinerings are unchanged.
Fixed
IfcCurveBoundedPlaneboundaries given as anIfcCompositeCurveof polyline segments compile withcompile-reference-backend(#336). They lower unchanged, as aCurveRelation::Compositewhose segments keep theirSameSense;axiolid-mesh-compile0.3.12 refused that relation ("is not a curve node"), and 0.3.13, the floor this crate already requires, resolvesCompositeandTrimmedboundaries (axiolid/kernel#255). No merge into one polyline is done here, so the segment structure stays in the neutral graph. A test now pins it: three segments with one reversed around a two-segment hole, and one reversed segment wrapping a clockwise ring, compile to their exact areas.
openbim-ifc
Changed (breaking)
- Behind
geometry, the re-exportedifc_geometryis 0.7 (was 0.6). Itsselect::subtypecarries the IFC4X3 ADD2 supertype chains (#293), sois_a, select membership andBodyKind::classifynow answer for IFC4X3-only entity names that previously classified as nothing; the newis_a_in/supertypes_of_inanswer for one named release.
[0.11.0] - 2026-10-08
ifc-geometry
Placement resolution covers every IfcObjectPlacement kind: linear placements compose with their alignment's frame (#357), grid placements resolve (#362), and any placement may be relative to a linear or grid one (#363). Error variants are added and resolved positions change for off-identity alignments and grid-placed products, so the next release is a minor one (0.11.0).
Added
IfcGridPlacementresolution (#362), inproduct_world_transform,products_world_transforms,PlacementResolver::world_transformand product lowering. Per IFC4.3 ADD2: the location is the intersection of the two axes' offset curves,OffsetDistances[1..2]to the LEFT of each axis ("anti-clockwise rotation through 90 degrees from the tangent"), reverted byIfcGridAxis.SameSense, andOffsetDistances[3]along the grid's Z; the x-axis is the first axis's tangent, anIfcDirection's x and y ratios, or the direction towards a secondIfcVirtualGridIntersection; z is the grid's Z; all of it in the frame of theIfcGridthat lists the axes, whoseObjectPlacementcomposes above. Straight axes (IfcLine, a collinearIfcPolyline, anIfcTrimmedCurveon a line, anIfcOffsetCurve2Dof one) intersect in closed form. Curved axes (IfcCircle,IfcEllipse, a bentIfcPolyline, anIfcTrimmedCurveon a conic with parameter trims, anIfcOffsetCurve2Dof one) intersect through the caller'sCurveEvaluator(LoweringSession::with_curve_evaluator,product_world_transform_with_evaluator, featurecompile); without one they are refused asUnsupportednaming the axis curve. Both the IFC2X3/IFC4 layout ofIfcGridPlacementand IFC4X3's, which prepends the inheritedPlacementRelTo, are read;GridPlacement::placement_rel_tois new.GeometryError::GridAxesParallel { intersection, axes },GridAxesDoNotIntersect { intersection, axes, detail }(curved axes that miss within their extent or meet more than once),GridAxisWithoutGrid { axis },GridAxesInDifferentGrids { intersection, grids }, andPlacementRelToConflict { placement, stated, implied }: a statedPlacementRelTothat resolves to a different frame than the alignment's or grid'sObjectPlacementIFC4.3 says it references. A grid withoutObjectPlacementisMissingAttribute.
Changed
- An
IfcLinearPlacementis placed in the frame its basis curve is stated in (#357), through both the cachedCartesianPositionand the derived path: itsPlacementRelTowhen stated, otherwise theObjectPlacementof theIfcAlignment(or other product) whose representation carries the basis curve. IFC4.3 ADD2 concept Product Linear Placement: "each product placement that uses Product Linear Placement references the IfcObjectPlacement of the IfcLinearPositioningElement through IfcLinearPlacement.PlacementRelTo";IfcObjectPlacement.PlacementRelTo: "If it is omitted, then in the case of linear placement it is established by the origin of horizontal alignment of the referenced IfcAlignment Axis".CartesianPosition, a fallback forRelativePlacement, is read relative to the same frame. Products on an alignment placed off identity used to be placed as if the alignment sat at the origin, andCachedPositionPolicy::Verifyrefused their correct caches; both are fixed.CachedPlacementMismatchreports world positions. The context'sWorldCoordinateSystemis still applied once, by the representation frame.derive_linear_placement_transformreturns the frame in the basis curve's coordinates, as before; its documentation now says so. PlacementResolver::world_transformwalksPlacementRelTothrough local, grid and linear placements alike (#363), caching each and keeping the cycle and depth refusals across mixed chains. A local placement relative to a linear placement used to beWrongEntityType, and one relative to a grid placementUnsupported. An entity that is noIfcObjectPlacementreportsexpected: "IfcObjectPlacement".
Added (#328, geometry in the bindings)
compile::Toleranceandcompile::TriMeshre-export the tolerance every compile entry point takes and the mesh it returns, so a caller that names noaxiolid-*crate (theopenbim-ifcfacade'smeshfeature) can call them. Behindcompile, as before.
Semver: additive; it ships with the minor release above (0.11.0), which the openbim-ifc facade's mesh feature needs.
openbim-ifc
Changed (breaking)
- Behind
step, the re-exportedParseOptionsandOnMalformedareopenbim-step0.11's (were 0.8's), throughifc-step(#288). 0.11 addsParseOptions::accept_real_without_point, included inParseOptions::lenient(): a lenient read keeps a REAL written without its decimal point (1E-05) with a diagnostic instead of skipping its record. Strict reads still refuse it.
[0.10.0] - 2026-10-04
ifc-geometry
Product lowering gains two opt-ins: Reference View openings taken as applied (#351) and an injected curve evaluator for linear placements (#353), which also checks a cached position (#354). The derived linear-placement frame now follows IFC4.3 (#355), which changes derive_placement_transform's output. API is added and behaviour changes, so the next release is a minor one (0.10.0).
Added
- Net lowering of hosts whose IFC4 Reference View openings carry no Body (#351).
lower::lower_product_net_with(session, product, NetOptions), withNetOptions::default().with_reference_only_openings( ReferenceOnlyOpenings::TakeAsApplied), lists anIfcOpeningElementwhose representations are allReferencein the newNetLowering::taken_as_applied(TakenAsApplied { opening, relation, reason: AppliedReason::ReferenceRepresentationOnly }) and subtracts nothing for it; a host whose openings are all taken as applied lowers to its gross Body. IFC4 ADD2 TC1IfcOpeningElement: a'Reference'representation "is not subtracted, it is provided in addition to the hole in the Body shape representation of the voided element", and its Reference View concept says it "shall not be used to subtract the opening".lower_product_netis unchanged and still refuses such a host (ADR 0014, amended). An opening with no representation, one with another representation besideReference, aReference-onlyIfcVoidingFeatureand an opening whose Body does not lower are refused whatever the option. - Evaluator-taking product lowering (#353), feature
compile:LoweringSession::with_curve_evaluator(&dyn CurveEvaluator)places anIfcLinearPlacementwithout a cachedCartesianPositionby deriving it from itsRelativePlacementthrough the caller's evaluator, for every entry point that takes the session (lower_product_items,lower_product_representation,lower_product_netand its openings). The same for callers without a session:product_world_transform_with_evaluator,product_representation_frame_with_evaluator, andconstraint::placement::derive::derive_linear_placement_transformfor one placement. AnIfcParameterValueon an alignment centreline is refused by name (#347). Without an evaluator nothing changes: the cache is read, and a placement without one is refused. - Cached-position check (#354), feature
compile: with an evaluator, a cachedCartesianPositionis compared with the derived location underCachedPositionPolicy::Verify(the default). Farther apart than the model's tolerance is the newGeometryError::CachedPlacementMismatch { placement, cached, derived, distance, tolerance }; within it, the derived frame is used, since IFC4.3 makes the cache "an optional fallback" for the linear expression. Only the location is compared.CachedPositionPolicy::Trust(LoweringSession:: with_cached_position_policy) uses a cache as it is and derives only uncached placements, for a model whose expressions this bridge cannot derive. The tolerance,derive::cached_position_tolerance, is the coarsestPrecisionof the model's 3D contexts, which IFC defines as "the tolerance under which two given points are still assumed to be identical", in the project length unit and converted to metres; IFC's default of 1e-5 project units when none is declared; floored at floating-point rounding (1e-9 relative, as the alignment seams). LoweringSession::derives_linear_placements.
Changed
derive_placement_transformbuilds the IFC4.3 frame from the evaluator's point and tangent (#355): axes(tangent, left, up), whereleftis the horizontalZ x tangentandupis perpendicular to the tangent in its vertical plane. A positiveOffsetLateralnow moves LEFT, asIfcPointByDistanceExpressionstates; it moved right, and the product's local Z lay along the lateral.derive_linear_placement_transformalso composes an explicitIfcAxis2PlacementLinear.Axis/RefDirectionin that frame (8.9.3.4), which was ignored.derive_placement_transformrefuses a distance when the evaluator'sdistance_conventionfor the basis curve is not the one IFC states: plan distance on anIfcGradientCurveorIfcAlignment, arc length on a polyline. The reference evaluator agrees on all of them.- The refusal of an uncached
IfcLinearPlacementnames the evaluator-taking entry points.
openbim-ifc
Changed (breaking)
- Behind
ifcxml, the re-exportedXmlCodecreads strictly by default when it has a schema: values are typed from the schema and undeclared names are refused (ifc-xml#266; see its changelog)..with_reading(SchemaReading::Lenient)restores the previous read.
Added
- Behind
ifcxml, re-exportsSchemaReadingandXmlLayoutbesideXmlCodecandXmlProfile, and with them the buildingSMART XSD layout readerXmlCodec::xsd(ifc-xml#265).
[0.9.0] - 2026-10-20
ifc-alignment
Fixed
- The writers' GlobalId check is
ifc_model::guid::Guid::parse(#492): 22 characters were counted, so a text outside the IFC base-64 alphabet or with a leading digit above3was staged. Both are now refused withAlignmentError::InvalidnamingGlobalId, before anything is staged. - The
authoring::layoutdocs no longer claimifc-authorowns anIfcRelNestswriter; they say how to stage the nesting (#492).
Semver: minor. A writer now refuses GlobalIds it accepted; a well-formed GlobalId behaves as before.
ifc-geometry
Input this crate refused now lowers exactly onto the station relations of axiolid-model 0.3.5 (ADR 0082), and a parameter along an alignment that a newer evaluator answered is refused. Behaviour changes and API is added, so the next release is a minor one (0.9.0).
Added
- IFC4X3 stations (#307), stored exactly, never evaluated:
IfcPointByDistanceExpressionlowers toGeometryNode::CurveStation.IfcAxis2PlacementLinearlowers toGeometryNode::OrientedCurveStation.AxisandRefDirectionpass through as components in the curve's (tangent, left, up) frame, which is how IFC4.3 ADD2 8.9.3.4 reads them.IfcOffsetCurveByDistanceslowers toCurveRelation::OffsetByStations. Offsets that stop short of the ends continue unchanged to them, as 8.9.3.42.3 states, through added stations when the basis states its length.IfcSectionedSolidHorizontallowers toSolidOperation::SectionsAtStations, andIfcSectionedSurfacetoSurfaceRelation::OpenSectionsAtStations, with theIfcOpenCrossProfileDef.Tagsas section tags (matched as sets, an open section forwards or reversed).- New entry points:
lower::lower_point_by_distance_nodeandlower::lower_axis2_placement_linear_node(modulelower::station). - The five families move from
dispatch::PLANNEDtoIMPLEMENTED, withPARTIALrows for their refusals.BodyKind::classifygivesIfcSectionedSolidHorizontalSectionedSpineand the two station itemsPoint.
- The conventions match IFC4.3 ADD2 exactly, so nothing is converted:
DistanceAlongis plan distance on anIfcGradientCurve(its parameter is the BaseCurve's, 8.9.3.34.1) and arc length elsewhere.OffsetLateralis positive to the left.OffsetVerticalis perpendicular to the tangent in its vertical plane (StationFrame::Section).OffsetLongitudinalruns along the tangent.
- Section profile axes follow the documented upstream fix, not the printed ADD2 sentences: profile Y =
Axis, normal =RefDirection, profile X =Axis x RefDirection(the left lateral by default). The sources are buildingSMART/IFC4.x-IF#147, IFC4.x-development#1010 and #1151, PRs #1162 and #1163, andIfcOpenCrossProfileDef8.15.3.15.1. 8.8.3.35.1 says profile X isRefDirection, and 8.8.3.37.1 says X isDirectrix x Axis, to the right. #344 tracks the upstream change.
Refused by name
DistanceAlongasIfcParameterValue, and a distance before the start or beyond a stated length.- A station on a tangent discontinuity of its basis, and a run of sections or offsets across one. IFC lets the previous segment's tangent govern (8.9.3.48.3) and mitres sections (8.8.3.35.1). The neutral evaluators read the next segment's tangent and do not mitre. Seams are read from stored data: polyline corners, and elevation-law breaks where the grade jumps. A basis whose seams cannot be located is refused: a curve relation, or a B-spline with a knot of multiplicity at least its degree.
- A frame that scales, mirrors or tilts the vertical. WR2 (parallel axes). 2D axis directions.
- On an offset curve: offsets short of an unbounded or unstated end, a non-zero
OffsetLongitudinal, and a member on another basis. - On the sections: the WHERE rules
NoLongitudinalOffsets,NoOffsets,CorrespondingSectionPositionsandSectionsSameType, positions off the directrix or out of order, mixed tagging, and branching breaklines.
Fixed
constraint::placement::derive::derive_placement_transformrefuses anIfcParameterValueDistanceAlongon anIfcGradientCurveorIfcAlignmentbasis curve by name (GeometryError::Unsupportedon the basis curve) and no longer hands it to the injectedCurveEvaluator(#347). IFC4.3 ADD2 does not define that parameter: a gradient curve takes itsBaseCurve's (8.9.3.34.1), a composite accumulates the parametric ranges of its parent curves (8.9.3.20.1), which are angles for a circle (8.9.3.18.1) andu = s / (A sqrt(pi))for a clothoid (8.9.3.19.1), andIfcCurveSegmentsays no parametric space is yet defined for its parent curves (8.9.3.28.1). The station lowering above refuses it the same way. A parameter on anIfcPolylineor line-onlyIfcIndexedPolyCurve, where IFC counts one per segment (8.9.3.51), is unchanged.- Released 0.3.0 through 0.8.1 passed that parameter through as the evaluator's native parameter. With
axiolid-evaluateup to 0.3.5 the reference evaluator refused it, so nothing was placed. Withaxiolid-evaluate0.3.6 (released 2026-10-04), which any of those releases resolves to, it reads the parameter as plan distance (axiolid ADR 0082) and answers: a product placed by anIfcParameterValuealong an alignment lands at that plan distance, a position IFC does not give it. Another injected evaluator received the same undefined value. Upgrade to get the refusal.
Changed
Cargo.locktakesaxiolid-construct0.3.15,axiolid-mesh-compile0.3.14 and, dev-only,axiolid-evaluate0.3.6 andaxiolid-reference0.3.7. The workspace minimums below stay: nothing here relies on the new versions.- The workspace requires
axiolid-model0.3.5. Withcompile-reference-backendit requiresaxiolid-mesh-compile0.3.13 andaxiolid-construct0.3.14, which resolve and mesh the station relations. Dev-only:axiolid-evaluate0.3.5 andaxiolid-reference0.3.6. - An
IfcCurveSegmentplaced by anIfcAxis2PlacementLinearis still refused. Its station lowers now, but no neutral relation places a curve in a station's frame (#311).IFCSEGMENTEDREFERENCECURVEstays inPLANNED(#311).
ifc-properties
Added (#469)
- Conversion between units.
ConversionUnitis a unit reduced to its SI dimensional exponents and its factor into SI base units, kept as a mantissa and a power of ten: from an SI name and prefix (ConversionUnit::si), a named conversion-based unit (ConversionUnit::named: the IFC4 Table 673 designationsinch,foot,yard,mile,square foot,acre,cubic foot,litre,gallon US,pound,degree,psi, ... with their exact legal factors where the table rounds, andUS survey foot; listed bynamed_units), text (ConversionUnit::parse:MILLIMETRE,MILLI METRE,SQUARE_MILLIMETRE,foot), or a model's unit entity (resolve_unit:IfcSIUnitprefixes,IfcConversionBasedUnitfactor chains andIfcDerivedUnitexponents through the exact resolver).unit_conversionchecks the dimensions agree and gives aUnitConversion; prefixed SI units convert by an exact power of ten (millimetres to metres divides by 1000).convert_valueandconvert_between(two unit entities of a model) convert one value. - Refused with
UnitConversionError: units of different dimensions,IfcMonetaryUnit,IfcContextDependentUnit, andIfcConversionBasedUnitWithOffsetor degree Fahrenheit by name, whose offset is not applied until #110 is settled. The SIDEGREE_CELSIUSconverts affinely. format_length: a length as decimal text in any length unit, or as feet and inches (5' - 6 1/2") or inches rounded to a fraction of an inch (LengthFormat).measure_unit_type: the unit type a measure takes in a release, as the exact resolver derives it (IfcNonNegativeLengthMeasuretakes aLENGTHUNIT);exact_project_unit: the project's unit of a type.
Changed (#469)
- The exact unit resolver also tracks each unit's factor as a mantissa and a power of ten.
ExactUnitand every answer are unchanged.
Semver: minor (additions).
openbim-ifc
Added
tests/stationing_template.rs: a referent'sPset_Stationingauthored byalignmentand read bypropertieschecks clean against theproperty-catalogIFC4X3 ADD2 corrected profile (#216).
Changed (breaking)
PanelPositionandUnreachableare#[non_exhaustive]: a match needs a wildcard arm.SectorandContainerElementsare#[non_exhaustive]; they can no longer be built with a struct literal outside the crate.Code previously behind
schemais behindschema-api, whichschemaand every release feature imply;schema::for_versionreturns aResult(seeifc-schema).
Changed
- The georeferencing and alignment conformance test gives its placeholder
IfcPolylinetwo points (PointsisLIST [2:?], checked since #111). - Door and window operation reads name the type-object entity per verified release (IFC2X3, IFC4, IFC4X3) and refuse any other with
ExactPropertyError::UnsupportedSchema, instead of treating every non-IFC2X3 release as IFC4.
Added
- Per-release schema features (#112):
ifc2x3,ifc4,ifc4x1,ifc4x2,ifc4x3, each providing the schema API with that one bundled table, andschema-api(the API with no table).schemakeeps its meaning: the API with every release. A single-release build refuses the others throughschema::for_versionwithschema::NotBundled. Domain features link the releases their crates read, so enabling one brings every release it reads. compiled_features()reportsschema-apiand each release feature.tests/intermediate_releases.rs: IFC4X1 and IFC4X2 files resolve to their ownSchemaVersion, release id and bundled table (#33).
[0.8.1] - 2026-10-10
ifc-alignment
Semver: a patch release. No public item or behaviour changes; the dependency floors rise with the workspace (ifc-geometry #418, #414).
Changed
- Requires
axiolid-curve0.3.6 (andaxiolid-evaluate0.3.10 for the tests), the releases that add the neutralCurvePath(axiolid/kernel#290) and measure stations along offsets (axiolid/kernel#289). Nothing here builds either; every test passes unchanged against them.
ifc-geometry
No public API changes. The reference backend's floor rises and input it refused now compiles, as with earlier kernel floors (0.3.1): the next release is a patch (0.8.1).
Changed
compile-reference-backendrequiresaxiolid-mesh-compile0.3.12 andaxiolid-construct0.3.13 (axiolid/kernel#245, #315). AnIfcCurveSegmentover a 2DIfcPolynomialCurve(theCUBICtransition, #90) lowers to a Bezier trimmed atTrimSelector::ArcLength. 0.3.4 read parameter selectors only and refused that trim by name; 0.3.12 resolves it by quadrature, so the segment compiles. A swept disk whose polyline directrix turns a sharp corner without a fillet radius compiles with an exact half-angle mitre, so its volume is the section area times the centreline length. 0.3.9 to 0.3.11 refused that corner, so a downstream build that resolved them fresh refused such pipes. The kernel still refuses three cases by name (axiolid/kernel#248): a sharp corner beside an arc, a closed polyline, andFilletRadiusequal toRadius.
ifc-properties
Added
PropertyIndex: the exact resolver's view of one model, built once byPropertyIndex::build(&model)and asked per object (#352). It validates everyIfcRelDefinesByPropertiesandIfcRelDefinesByTypeonce and records each object's assignments, so resolving every object is linear in objects plus relationships instead of their product. Itsexact_property,exact_properties,exact_properties_where,exact_property_sets_whereandexact_predefined_setsanswer exactly what the free functions of the same name answer, refusals included, in the same precedence. It borrows the model (ADR 0003), so the model cannot be edited while an index exists; rebuild it after a commit. Measured on a generated IFC4 model (benches/properties,benchmarks/baseline.md): one property for every one of 100,000 walls takes 2.0 s instead of about 31 hours (extrapolated; 10,000 walls: 195 ms instead of 814 s), scaling ten-fold per ten-fold more walls.
Changed
- The free functions scan the relationships for the one queried object through the same code, so their answers are unchanged (a dump of every answer over the fixture corpus and the new equivalence models is byte-identical before and after); with
ifc-schema's precomputed tables a single call is several times cheaper, but it still validates every relationship of the file, as it must.
Semver: one new public type, nothing changed or removed: a patch release.
openbim-ifc
Changed
- Requires the patch releases published with it:
ifc-properties0.5.1 (bare quantity values, IFC2X3*_with_owner_historyauthoring, type objects in the exact API),ifc-cost0.2.2,ifc-classification0.2.2 (IFC4X3 binding) andifc-systems0.2.2. door_operationandwindow_operationgiven a type object (anIfcDoorType, IFC2X3IfcDoorStyle, ...) refuse it withNotADoororNotAWindow, where they returnedProperty(InvalidQueryObject): the exact property reader now accepts type objects (ifc-properties #193). Results and refusals for doors and windows are unchanged.
[0.8.0] - 2026-10-09
ifc-alignment
Semver: a minor release (0.8.0). No public item changes, but lower_segmented_reference_curve and segmented_reference_curve3 now return a curve for input they refused, and its pivot law can hold a held-rail piece (CantForm::AboutRail), which only axiolid-curve 0.3.5 and axiolid-evaluate 0.3.9 or later read. Dependency floors rise to those releases.
Added
lower_segmented_reference_curve/segmented_reference_curve3lower aVIENNESEBENDthat rotates about a held rail (#364): when one rail has the same height at both ends (at the model's declared precision, asCantLayoutcompares it), the pivot law is Axiolid's held-rail piece,CantPiece::about_rail, on that rail at its authored height, and the rotation pointheld -+ b sin(psi) / 2follows the bend's angle law (axiolid/kernel#279, Axiolid ADR 0081 amendment). The banked curve andCantLayout::frame_at_distancenow agree inside such a bend: cant, bank angle and rotation point to rounding; the rail heads within theBankConvention::TangentRotationdrift(D / 2)(1 - cos theta), the banked section's rails standing square to the tangent while the cant layout states them vertically (under 0.02 mm for 150 mm cant on a 2% grade, nothing on the level).
Changed
- The rotation stays
BankConvention::TangentRotation: IFC4.3 ADD2 states cant as the anglepsi = arcsin(D / b)(IfcAlignmentCantSegmentTypeEnum, 8.7.2.1) andIfcSegmentedReferenceCurve(8.9.3.62) carries it by interpolating the placement axes, a rotation of the section frame. A held rail therefore drifts by(D / 2)(1 - cos theta)from its authored height on a grade,thetathe climb of the rotation point's path; this is documented incurve/reference.rsandcant/frame.rs. - The
Unsupportedreason naming the missing angle-form pivot is gone. A Viennese bend whose pivot moves with neither rail held is still refused by both the lowering andCantLayout, with the same typed error. - Requires
axiolid-curve0.3.5 (the held-rail pivot), and for the testsaxiolid-evaluate0.3.9, which evaluates it.
ifc-geometry
Input this crate refused now lowers exactly, onto the Axiolid relations of axiolid-curve 0.3.3 and axiolid-model 0.3.4: behaviour changes, so the next release is a minor one (0.8.0).
Changed
- An
IfcCurveSegmentover a 2DIfcPolynomialCurve(theCUBICtransition) lowers to its exact Bezier, placed rigidly and trimmed atTrimSelector::ArcLength(SegmentLength), instead of refusing (#90). A non-zeroSegmentStart, a backwards walk, a 3D polynomial, and one with no degree-one coordinate to bound the trim stay refused by name. - An
IfcGradientCurvewhose base curve holds such a segment has aCurve2::Chainplan: the polynomial is a parametric piece read by arc length (#90). - A vertical
IfcCirclesegment in anIfcGradientCurvelowers toElevationLaw::CircularArc, and a verticalIfcSpiral(theIfcClothoid) toElevationLaw::Intrinsic, its extent read from the next segment's start (#258). dispatch::PLANNEDkeepsIFCPOLYNOMIALCURVE(unbounded on its own; it lowers as anIfcCurveSegmentparent) andIFCSEGMENTEDREFERENCECURVE, now citing #311: the roll law exists, but the geometric form states cant through stations (#307) and parent curves with no normative mapping. AnIfcAxis2PlacementLinearplacement now cites #307.
ifc-properties
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with property sets shrinks from 1,519,870 to 926,986 bytes. The default build links every release, as before. - A file declaring IFC2X3, IFC4 or IFC4X3 when the build leaves that release out is refused by the exact reads with
UnsupportedSchemainstead of panicking. Permissive reads (type objects inproperty_sets_by_object, template reads) keep the IFC4 table as their baseline and fall back to the newest bundled one in a build without it. A build naming no release fails to compile with a message naming the features, so a build without default features has to name one.
Under 0.x, a minor release.
openbim-ifc
Changed (breaking)
propertiesre-exportsifc-properties0.5.0, whose breaking changes pass through (see that crate's changelog):QuantityandQuantityKindare#[non_exhaustive], and gainQuantity::Unresolvedfor a quantity without a readable value (#138) andQuantityKind::Numberfor IFC4X3IfcQuantityNumber;create_quantity/create_quantity_withtake the&Modelthey write into and write its release's layout;PropertyTemplategains fields and is#[non_exhaustive](#108).
- Requires
ifc-spatial0.2.3 (referenced_elements, release-bound container classification) andifc-geometry0.4.3 (BodyItem::item_world, #185).
Added
spatial_properties(model)(featuresspatialandproperties): every spatial container in tree order, depth first from the roots, with the elements it holds, each with itsexact_propertieslist, in IFC2X3, IFC4 and IFC4X3 (#121). An element is listed asContained(IfcRelContainedInSpatialStructure),Referenced(IfcRelReferencedInSpatialStructure, so an element spanning several storeys appears under each) orPart(anIfcRelAggregatespart, at any depth, of a contained element, which the Element Composition concept places by its composite's containment), ordered by element id. A nested space is its own container, not folded into its storey. A model-level refusal (diagnostics, missing or unsupported schema) is the function's error; any otherExactPropertyErroris reported on the element it concerns and the other elements are still returned. Properties resolve lazily asContainerElements::elements(orelements_where, withexact_properties_whereselectors) is iterated. New types:SpatialProperties,ContainerElements,SpatialContainer,ContainerName,ElementMember,ElementPropertiesandSpatialMembership. Needs the nextifc-spatialrelease, which addsSpatialTree::referenced_elements.
Changed
- The
spatialfeature classifies spatial containers from the file's declared release (#121, via the nextifc-spatialrelease, which now linksifc-schema): IFC4X3 facilities and facility parts such asIfcRoad,IfcRoadPart,IfcBridgeandIfcBridgePart, andIfcExternalSpatialElement, are containers, soSpatialTree,spatial_propertiesandunreachable_productssee the elements placed in them. Containment or reference into a non-container is reported as aSpatialAnomaly.unreachable_productsskips containers by the tree's classification instead of a name test.
[0.7.3] - 2026-09-27
openbim-ifc
Changed
- The crate README, which is the crates.io page, is rewritten. The published one told readers to depend on a Git revision and said the crates were not on crates.io. The code is unchanged since 0.7.2.
[0.7.2] - 2026-10-09
ifc-alignment
Semver: a patch release. No code, API or behaviour changes; only the dependency floor rises with the workspace (#346).
Changed
- Requires
axiolid-model0.3.7 (andaxiolid-evaluate0.3.8 for the tests), the releases that measure stations along curve relations (axiolid/kernel#285) whichifc-geometrylowers onto. Nothing here reads a station; every test passes unchanged against them.
openbim-ifc
Added
window_operation(model, window)(featuresgeometry-selectandproperties), the window counterpart ofdoor_operation: each panel of a window as a world frame, width, height, hinge side, swingSectorfor a side hinge and tiltSectorfor a top or bottom hinge, from its placement,OverallWidth/OverallHeight, partitioning (IfcWindowType.PartitioningType, IFC2X3IfcWindowStyle.OperationType, or the occurrence's),IfcWindowPanelPropertiesand the mullion and transom offsets ofIfcWindowLiningProperties, in IFC2X3, IFC4 and IFC4X3 (#170). Single, double and triple partitionings with side-hung, tilt-and-turn, top- and bottom-hung, sliding, removable and fixed panels are derived;NOTDEFINEDandUSERDEFINEDpartitionings, pivot,OTHEROPERATIONandNOTDEFINEDpanels, a window without panel properties or overall size, panels that contradict the partitioning, and a split without a valid lining offset are refused asWindowOperationError, never defaulted. Panels tile the placement's XZ plane; lining, mullion and transom thicknesses are not applied.SectorandSideare shared withdoor_operation, unchanged.
[0.7.1] - 2026-10-09
ifc-alignment
Semver: a patch release. No code, API or behaviour changes; only the dependency floor rises with the workspace (#346).
Changed
- Requires
axiolid-model0.3.6 andaxiolid-curve0.3.4 (andaxiolid-evaluate0.3.7 for the tests), the releases that add seam sides to stations (axiolid/kernel#263) whichifc-geometrylowers onto. Nothing here reads a station; every test passes unchanged against them.
openbim-ifc
Added
profile_outlineandProfileOutlineare re-exported at the root undergeometry-select(#166), besidedescribe_profile: the straight-edged outline of an arbitrary closed profile, without the geometry kernel.
Changed
- Requires
ifc-geometry0.4.1, which carriesprofile_outline,product_representation_frameand the curve-bounded plane fix (#163).
[0.7.0] - 2026-10-20
ifc-alignment
Behaviour fixes with no API change, but a patch release is not enough: input that evaluated before is now refused (a Viennese bend whose rotation point moves with neither rail held), and the banked lowering's refusal reasons change, so the next release is a minor one (0.7.0).
Added
cant_at,CantLayout::cant_at_distanceand soCantLayout::frame_at_distanceevaluate aVIENNESEBENDthat rotates about a held rail (#364): when one rail has the same height at both ends (compared at the precision the rotation points are compared at), that rail stays and the other is the held rail+- D(xi), withD = b sin(psi(xi))from the bend's angle law. Rotation about the low rail, the usual case in rail practice, now evaluates inside the bend: fromD = 0to0.15 monb = 1.5 mabout the right rail, the left rail mid-bend is 0.0750942 m.
Fixed
cant_at,CantLayout::cant_at_distanceand soCantLayout::frame_at_distanceevaluate aVIENNESEBENDfor the section's cantD = left - right, as IFC4.3 ADD2 writes it (IfcAlignmentCantSegmentTypeEnum, 8.7.2.1):psi = arcsin(D / b),psi(xi) = psi1 + dpsi xi^4 (35 - 84 xi + 70 xi^2 - 20 xi^3),D = b sin(psi), with the railse +- D / 2about a rotation pointe = (left + right) / 2that stays put (#312). They no longer blend each rail's height as if it were a cant: rotating about the centreline fromD = 0to0.15 monb = 1.5 m, the cant mid-bend is now 0.0750942 m, not 0.0750235 m. Inside a bendCantFramenow agrees with the banked centreline (Curve3::Banked) and with the horizontal Viennese-bend law, which already usedD.
Changed
- Inside a
VIENNESEBENDwhose rotation point moves with neither rail held, cant evaluation now returnsAlignmentError::Unsupported, as the banked lowering does: the standard states the bend for the section's cant angle only, so these rails are not determined. The authored start and end values still evaluate, so the horizontal Viennese-bend law, which reads the cant at its own ends, is unaffected where those meet the cant bend's ends.CantLayoutcompares the two rotation points at the model's declared precision, as the banked lowering does, soCantLayout::resolveof a layout holding a Viennese bend now also refuses a malformedIfcGeometricRepresentationContext.Precision.cant_atalone compares them to floating-point rounding. - A Viennese bend's
|D| > bcheck applies to the cantD, not to each rail's height. lower_segmented_reference_curve/segmented_reference_curve3refuse the same Viennese bends as before, now with two distinctAlignmentError::Unsupportedreasons: a held rail (the low rail, say), whose rotation pointheld -+ b sin(psi) / 2is an angle form that Axiolid's height-form pivot law cannot carry (BankError::AngleInPivot) untilaxiolid-curvehas an angle-form pivot (#364); and a pivot that moves with neither rail held, ascant_atgives. For a held-rail Viennese bend the section frame therefore has a value where the banked curve refuses; everywhere else they agree.
ifc-geometry
select::subtype carries IFC4X3 ADD2 supertype chains (#293). Answers change for IFC4X3-only entity names, and a build without default features links fewer schema tables (#306), so the next release is a minor one.
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table. The default build links every release, as before, but a build without default features (the kernel-free column,openbim-ifc'sgeometry-select) now links only the releases it names; release-bound authoring refuses the others withGeometryError::AuthoringSchemaUnbound. Representation selection reads no table and is unaffected.loweringstill links every release throughifc-alignment.
Added
select::is_a_in,supertypes_of_inandknown_entities_inanswer for one named release, andVERIFIED_SCHEMA_VERSIONSlists the releases they answer verbatim: IFC4 ADD2 TC1 and IFC4X3 ADD2.tables_are_verified_for(Ifc4x3)is nowtrue.- The IFC4X3 rows are a delta over the IFC4 tables: the 19 concrete geometry, profile and placement entities IFC4X3 adds, the abstract supertypes they introduce (
IfcSpiral,IfcSegment,IfcOffsetCurve,IfcDirectrixCurveSweptAreaSolid,IfcSectionedSolid), and the IFC4X3 chains of the four IFC4 entities IFC4X3 re-parents.tests/schema_coverage.rscompares every chain, per release, withIFC4.expandIFC4X3_ADD2.exp.
Changed
- The release-neutral
is_aandsupertypes_ofnow resolve IFC4X3-only entities with their IFC4X3 chain:is_a("IFCCLOTHOID", "IFCCURVE"),is_a("IFCGRADIENTCURVE", "IFCCOMPOSITECURVE"),is_a("IFCTRIANGULATEDIRREGULARNETWORK", "IFCTESSELLATEDFACESET")andis_a("IFCOPENCROSSPROFILEDEF", "IFCPROFILEDEF")werefalseand aretrue. Every answer for an entity IFC4 declares is unchanged, including the four IFC4X3 re-parents (IfcOffsetCurve2D,IfcOffsetCurve3D,IfcFixedReferenceSweptAreaSolid,IfcSurfaceCurveSweptAreaSolid), which keep their IFC4 chain unless a caller asksis_a_in(Ifc4x3, ..). - Visible effects: the select resolvers (
GeometricSetSelect,BooleanOperand,PointOrVertexPoint, ...) accept IFC4X3-only members instead of reporting a wrong type.BodyKind::classifyreturnsCurvefor the sixIfcSpiralsubtypes,IfcPolynomialCurve,IfcOffsetCurveByDistancesandIfcSegmentedReferenceCurve, andSurfaceforIfcSectionedSurface, so body description lists those items instead of refusing them. Lowering is unchanged: those families still lower, or refuse with thedispatch::PLANNEDreason, as before. Where-rules that test inheritance (dimensionality, swept-area rules) now also apply to the IFC4X3 subtypes the schema makes them inherit.
ifc-georef
Added
- IFC2X3 georeferencing through the
ePSet_MapConversionandePSet_ProjectedCRSproperty sets (#451):resolve_property_set_georeferencingresolves them into the sameProjectToMapas IFC4'sIfcMapConversionandIfcProjectedCRS, with the project and map length units resolved, andhas_property_set_georeferencingtests for them cheaply. The source is the buildingSMART Australasia "User Guide for Geo-referencing in IFC" v2.0 (2020), §5.1 Tables 2 to 4: the property names and measure types, and the sets onIfcProject. Where the guide is silent the reader follows established practice, documented as such: set names match ignoring case (ePSet_,ePset_,EPset_),IfcSiteis a legacy host read only when the project carries no set, and an optionalMapUnittext names an SI length unit. Precedence: the declared release decides, IFC2X3 through the sets and IFC4/IFC4X3 through the entities, never merged. OperationSource::PropertySet(the host object), and theMAP_CONVERSION_PSETandPROJECTED_CRS_PSETnames.- Typed refusals for a malformed or incomplete set:
GeorefError::IncompletePropertySets(one set of the pair, never joined across hosts),AmbiguousPropertySets(two sets of one name on a host),MissingProperty(a required property without a value) andInvalidProperty(not a single value of the guide's type, not finite, or a length whose own unit is not the map unit). A repeated property name isRuleViolationWR32. - Fixtures
test/fixtures/synthetic-georef/fromtools/gen_georef_fixtures.py: an IFC2X3 file checked against its IFC4 twin, the site fallback, and refusal cases.
Semver: minor. Additions only; the new enum variants are on #[non_exhaustive] enums.
ifc-properties
Added
create_monetary_unit(tx, model, draft)stages anIfcMonetaryUnitin the model's declared release (#232).Currencyis anIfcCurrencyEnumin IFC2X3 TC1 and anIfcLabelfrom IFC4 on: IFC2X3 writes the enumerator token (.EUR., matched ignoring case) and refuses a currency the enumeration does not list withPropertyError::AuthoringInvalid; IFC4 and IFC4X3 write the text given. A header binding no single supported release is refused withMultipleSchemasorUnsupportedSchema, as for the other release-bound writers.PropertyError::ValueForm { entity, attribute, declared, typed_required, found }(#215): a value of the declared type written in the form ISO 10303-21 does not use for it, mirroringifc-author'sAuthorError::ValueForm.PropertyErroris#[non_exhaustive], so the new variant is additive.
Changed
- Behaviour change: every writer of an
IfcValueslot refuses a bare literal before staging (#215).IfcValueis a SELECT in every release, whose members are all defined types, so its value is written as a typed parameter (IFCLENGTHMEASURE(2.5), §12.1.8); a bare2.5was accepted and then reported byifc-validateastype.select.untyped. Covered:add_property_single_value(NominalValue),add_property_enumerated_valueandadd_property_enumeration(EnumerationValues),add_property_bounded_value(each bound and the set point),add_property_list_value(ListValues),add_property_table_value(DefiningValues,DefinedValues) andadd_measure_with_unit(ValueComponent). A bare number, string, boolean, logical or binary isPropertyError::ValueFormwithtyped_required: true; a wrapper around a wrapper isValueFormwithtyped_required: false; a reference, aggregate, enumeration constant,*, or a wrapper around$isPropertyError::AuthoringInvalid. Callers passingValue::Real(..)and the like must wrap the value in the measure it is (Value::Typed { type_name: "IFCLENGTHMEASURE", .. }). Which member a wrapper names is not checked here: these writers take no model, andifc-validatejudges it against the declared release.
Deprecated
add_monetary_unit: it takes no model and always writes IFC4 text, which is schema-invalid in IFC2X3. Usecreate_monetary_unit(#232).
Fixed
unit()andproject_units()read an IFC2X3IfcMonetaryUnitwhoseCurrencyis anIfcCurrencyEnumtoken:UnitKind::Monetarynow carries the currency instead ofNone, and the quantity unit check that consumes it sees the currency too (#232).
ifc-validate
Added (#461)
express: an EXPRESS rule evaluator (featureexpress, default).ExpressRules::parsereads a release's.exptext, which the caller supplies (the schemas are CC BY-ND 4.0; nothing of their rule text is bundled or generated), checks it against the bundled tables (same schema, same slots for every entity) and resolves every name.validate_with_optionswithValidateOptions::expressthen evaluates every entity WHERE rule (inherited ones on every subtype), every type WHERE rule (wherever a value of the type appears: directly, in an aggregate, through a defined type or wrapped in a SELECT) and every global rule, with the FUNCTIONs they call, derived attributes and INVERSE attributes, under ISO 10303-11 three-valued logic. Every declaration of IFC2X3 TC1, IFC4 ADD2 TC1, IFC4X1, IFC4X2 and IFC4X3 ADD2 is evaluable (ExpressRules::coverage;data/express-coverage.tsv): 339, 652, 658, 672 and 752 entity rules. A rule evaluating FALSE is an error under itsEntity.Labelid (Type.Label,global.Rule); UNKNOWN or?is the newSeverity::Indeterminate; a construct the evaluator lacks is reported once as unsupported, by name; data a rule cannot read is an evaluation error.EvalLimitsbounds steps per rule, nesting and the whole run; each limit is a typedBudgetExceeded. Supplied rules replace the hand-written ones with the same id; on the corpus and on every hand-written rule's adversarial pair the two agree. On this repository's fixtures, IfcOpenShell's rule and validation fixtures and the IFC4 annex E examples, verdicts match IfcOpenShell'sexpress_rulesbut for the differencesdocs/coverage.mdlists.structure::inverse_cardinality(structure.inverse.cardinality): every INVERSE clause's bounds, a single inverse meaning exactly one referrer. Off by default (ValidateOptions::inverse_cardinality), on with EXPRESS rules; it matches IfcOpenShell's findings on the corpus.ValidateOptionsandvalidate_with_options: budget, EXPRESS rules, INVERSE cardinality and a property-set catalog in one run.Finding::detail(FindingDetail): an EXPRESS result's release, scope (RuleScope), label andRuleOutcome, or aReservedSetName.Finding::indeterminate,Finding::with_detail,Summary::indeterminate.
Added (#511)
property.reserved_set_name: anIfcPropertySetorIfcElementQuantitywhose name starts withPset_orQto_(any case) but which the release's PSD/QTO catalog does not define is a warning (bSI PSE001). The catalog is supplied through thePropertySetCatalogtrait (ValidateOptions::catalog); this crate links none. Without one, such names are reported once as unsupported.
Changed
Summaryprints its indeterminate count:0 errors, 0 evaluation errors, 0 warnings, 0 indeterminate, 2 unsupported.validatereports a file usingPset_/Qto_names with one unsupportedproperty.reserved_set_namefinding when no catalog is supplied.global.UniqueGlobalIdruns under IFC4X1 and IFC4X2 too.
Semver: minor (0.7.0). Severity, Finding and Summary are #[non_exhaustive], so their new variant and fields break no caller; the new findings and the summary text change what a report holds.
Changed (#467)
Semver: minor. ValidateError is #[non_exhaustive] and gains a variant; a new finding id.
validate_declaredrefuses a file declaringIFC4X3_TC1orIFC4X3_ADD1with the newValidateError::UnpublishedSchema(token)instead ofUnknownSchema: a real IFC release, but one buildingSMART publishes no EXPRESS schema for, so no tables exist and it is not validated as IFC4X3 ADD2 by assumption.- The header check reports such a token as the warning
header.schema.unpublished, naming the release (IFC 4.3.0.1 or 4.3.1.0) and carryingFindingDetail::UnpublishedSchema(release), instead ofheader.schema.unknown. A caller that chooses to validate such a file as ADD2 passes those tables tovalidateitself; the report then carries this warning, so it never reads as a check against the declared release.
openbim-ifc
Changed (breaking)
- Re-exports three crates whose breaking releases pass through:
validateisifc-validate0.3.0 (Severity::EvaluationError, renamed rule ids,Budget::max_depthremoved),materialisifc-material0.3.0 (views and authoring bound to the declared release;create_materialtakes the model), andgeometryisifc-geometry0.4.0 (typed reference errors,IfcBlockmeshes no longer offset by half their extents). See each crate's changelog. - Requires the releases published with it:
ifc-properties0.4.1,ifc-spatial0.2.2,ifc-xml0.2.1,ifc-alignment0.3.1,ifc-author0.2.1,ifc-control0.2.1,ifc-cost0.2.1,ifc-tabular0.2.1 andifc-template-catalog0.2.1.
Added
body_description,describe_profileand their types (BodyDescription,BodyItem,BodyKind,SweptSolid,SweepPath,ProfileDescription,ProfileParameters) are re-exported at the root undergeometry-select(#147), so a rule check reads a body's kind and swept-solid profile parameters without linking the geometry kernel.door_operation(model, door)(featuresgeometry-selectandproperties): each leaf of a door as a world frame, width, hinge side and swingSector, from its placement,OperationTypeandIfcDoorPanelProperties, in IFC2X3, IFC4 and IFC4X3 (#148). Single and double swing, double-acting, sliding, rolling-up and swing-fixed doors are derived;NOTDEFINED,USERDEFINED, revolving, folding, lifting and theDOUBLE_DOOR_SINGLE_SWING_OPPOSITE_*operations, a door without panel properties orOverallWidth, and panels that contradict the operation are refused asDoorOperationError, never defaulted. The leaves lie on the placement's x axis; lining offsets across the wall depth are not applied.
Changed
- The
propertiesfeature also namesifc-schema, whichifc-propertiesalready links, so the door join reads attributes by name from the bound release's table. No crate is added to a build.
[0.6.1] - 2026-10-03
ifc-geometry
IFC4X3 ADD2 geometry families (#243). Every concrete IFC4X3 representation item and profile is now lowered or refused with a named reason.
Added
- IFC4X3 alignment curves lower exactly.
IfcCurveSegmentlowers forIfcLine,IfcCircle, 2DIfcPolylineand all sixIfcSpiralparents, placed by itsPlacementand cut by itsIfcLengthMeasurearc lengths: a polyline, an angle-trimmed circle, or a planarCurve3::Intrinsiccarrying the spiral's curvature law rebased to the segment in closed form. A negativeSegmentLengthwalks the parent backwards; the zero-length closing segment is its placement, and a composite drops it.IfcCompositeCurvenow acceptsIfcCurveSegmentmembers. - The spiral laws read the IFC4.3 terms:
sign(A_n) s^n / |A_n|^(n+1)for the clothoid and the second-, third- and seventh-order polynomial spirals;1/A_0 + cos(pi s/L)/A_1(cosine) and1/A_0 + sign(A_1) s/A_1^2 + sin(2 pi s/L)/A_2(sine), withLthe using segment's length. A present zero term is refused, not read as absent. IfcGradientCurvelowers toCurve3::Elevated: itsBaseCurveas one intrinsic plan with a piecewise curvature law, its vertical segments (IfcLinegrades, degree-2IfcPolynomialCurveparabolas) as a piecewise elevation law. Seams are checked in closed form: headings everywhere, positions after a line or arc, parabola arc lengths.- IFC4X3
IfcOpenCrossProfileDeflowers exactly throughlower_open_profile_nodeto an open polyline whose vertices are the closed-form sums of its widths and slopes (horizontal or along-slope widths,OffsetPointhonoured, slopes measured from +X towards +Y as the IFC4.3 figure states).describe_profilereads it as the newProfileParameters::OpenCross, in metres and radians, refusing aWidths/SlopesorTagscount mismatch, a negative width and a vertical slope with horizontal widths. It bounds no area, so the area-profile path refuses it, as forIfcArbitraryOpenProfileDef.section_slot::OC_*are its slot constants. - IFC4X3
IfcDirectrixDerivedReferenceSweptAreaSolidlowers to the same exactFixedReferenceSweepas its supertype when the directrix defines only a tangent, which IFC4.3 says is the identical behaviour. A directrix that defines a tangent plane (built fromIfcCurveSegments, such asIfcGradientCurveandIfcSegmentedReferenceCurve, or lying on a surface) is refused asUnsupportednaming the missing neutral primitive, the publiclower::swept::DIRECTRIX_DERIVED_TANGENT_PLANE. IfcTriangulatedIrregularNetwork(IFC4X3) lowers as a triangle mesh when everyFlagsvalue is a breakline code (0 to 7). A void or hole triangle (-2, -1) or an undocumented code is a typedUnsupportedrefusal; aFlagslist whose length differs from the triangle count isDegenerate.solid::tessellated::TriangulatedIrregularNetworkis the borrowed view.BodyKind::classifyandSolidKind::classifyreport it as tessellated.lower::dispatch::SPECIALISATIONS(andSpecialisation): subtypes routed to their supertype's lowering, each naming the attributes it adds. A test checks every row against the IFC4X3 schema.tests/schema_coverage.rsalso enumerates IFC4X3 ADD2. Its inventory is checked against the bundled table andIFC4X3_ADD2.exp. Every IFC4X3 representation item must be inIMPLEMENTED, inPLANNED, or have a nested disposition. Every IFC4X3 profile must be read or inUNLOWERED.- Fixtures
synthetic_ifc4x3_alignment_curves.ifcandsynthetic_ifc4x3_geometry_families.ifc, with their generatorstools/gen_ifc4x3_curve_fixtures.pyandtools/gen_ifc4x3_geometry_fixtures.py.
Changed
lower::dispatch::PLANNEDlists the IFC4X3 representation items that are not lowered, each with its runtime refusal text, instead of the generic "representation item family is not lowered yet":- a spiral or
IfcPolynomialCurveon its own (unbounded; each lowers as theParentCurveof anIfcCurveSegment); IfcSegmentedReferenceCurve(cant has no neutral roll law, #93);IfcSectionedSolidHorizontalandIfcSectionedSurface(no neutral sectioned sweep over curve-measure stations, nor a sectioned-surface relation);IfcOffsetCurveByDistances,IfcPointByDistanceExpressionandIfcAxis2PlacementLinear(no neutral distance-along-curve relation).
- a spiral or
- Further typed refusals, recorded in
dispatch::PARTIAL: anIfcPolynomialCurveparent trimmed by arc length and a parabola without a stated end (#90);IfcAxis2PlacementLinearsegment placements (#93); vertical arcs and clothoids (#258);IfcParameterValuemeasures; plan kinks, gaps and profiles that do not span the plan.
Fixed
- An
IfcCurveSegmentreached through a path that cannot lower it (a sweepStartParam/EndParamrange, a p-curve, a profile boundary) is refused by name. Before, theIfcCompositeCurveSegmentslots were read, soSegmentLengthwas taken forParentCurveand the result was a misleading wrong-value-kind error.curve::composite::CURVE_SEGMENT_UNREADis the view's backstop refusal. constraint::placement::derive_placement_transformresolves anIfcLinearPlacementwhose basis curve is anIfcGradientCurvethrough the gradient-curve lowering. Before, it passed the curve toifc_alignment::gradient_curve3, which expects anIfcAlignment, so the path always refused.- The
dispatch::PLANNEDdocumentation claimed every recognized representation item is lowered. It now lists the IFC4X3 families that are not.
[0.6.0] - 2026-10-03
ifc-alignment
Input this crate refused now lowers exactly, onto the Axiolid relations of axiolid-curve 0.3.3 and axiolid-model 0.3.4 (kernel#238, #239, #240), and HorizontalPlan::curve can now be a Curve2::Chain: behaviour changes, so the next release is a minor one (0.6.0).
Added
lower_segmented_reference_curvelowers an alignment with cant to an exactCurve3::Bankedinstead of refusing it, andsegmented_reference_curve3returns that curve directly (#93). The cant law isD = left - rightand the pivot(left + right) / 2, oneCantPieceper cant segment in the IFC4.3 base formula (the Helmert transition as its two halves, the Viennese bend as its bank angle); the section rolls about the 3D tangent byarcsin(D / b)(BankConvention::TangentRotation, the reading IFC4.3 ADD2 states).
Changed
- A
CUBIChorizontal segment lowers on every path (#90): per segment and in the composite as its exact cubic Bezier trimmed atTrimSelector::ArcLength(SegmentLength), and in the plan as aChainPiece2::Parametricread by arc length, which makeslower_horizontal_planreturn aCurve2::Chainfor a layout holding one. The seam after aCUBICreports its position (and heading) asSeamCheck::Authored. - A vertical
CIRCULARARClowers toElevationLaw::CircularArcfromStartHeight,StartGradientand the signedRadiusOfCurvature, on the per-segment path as the circle itself trimmed by angle (#258). A radius turning against the authored grades, or an arc turning vertical before its end, isInvalidSegment. - A profile starting before the plan rebases a straddling circular arc exactly, as it already rebased a polynomial piece.
Still refused, by name
- A vertical
CLOTHOID: its segment states neither end curvature (RadiusOfCurvatureis defined for arcs and parabolas only), so its law is undetermined. - A
CUBICthat starts curved: IFC4.3 defines onlyy = x^3 / (6 R L)leaving a straight. - A pivot that moves through a Viennese bend (Axiolid's
BankError::AngleInPivot), a cant layout that does not span the plan, and|D| > b.
ifc-geometry
Changed (breaking)
- Requires
ifc-alignment0.5.derive_placement_transformtakes anifc_alignment::PointByDistance, so the alignment version is part of this crate's public API; mixing it withifc-alignment0.4 types no longer compiles. No behaviour changes in this crate.
ifc-georef
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with georeferencing (and the property sets it needs) shrinks from 1,583,507 to 990,688 bytes. The default build links every release, as before. GeorefView::for_modelrefuses an IFC4 or IFC4X3 file whose table the build leaves out withGeorefError::UnsupportedSchema. The semantic column (--no-default-features) therefore names its releases too, e.g.--no-default-features --features ifc4,ifc4x3.
Under 0.x, a minor release.
ifc-properties
Changed (breaking)
Comparisonis#[non_exhaustive]: a match needs a wildcard arm.- The read results
ExactProperty,ExactPropertyEntry,ExactTableRow,Property,PropertySet,QuantitySet,ResolvedSetandPropertySetTemplateare#[non_exhaustive]; compare their fields instead of building one with a struct literal. - Every authoring draft is
#[non_exhaustive], so a struct literal no longer compiles outside the crate. Each gainsnew(required…)and one builder setter per other field, named after the field and taking the unwrapped value:TableValueDraft::new(name),DoorLiningDraft::new(),WindowLiningDraft::new(),ReinforcementBarDraft::new(total_cross_section_area, steel_grade),SectionReinforcementDraft::new(longitudinal_start_position, longitudinal_end_position, reinforcement_role, section_definition, cross_section_reinforcement_definitions),SiUnitDraft::new(unit_type, name),MonetaryUnitDraft::new(currency)andConversionBasedUnitDraft::new(unit_type, name, conversion_factor, dimensions). Fields stay public.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - The unique-member-name rule of complex properties and quantities is labelled per verified release only; another release is refused with
UnsupportedSchemarather than given the IFC4 label. - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3. - Exact value checks treat a type declaration form
ifc-schemaadds later as not matching; followsifc_schema::TypeKindbecoming#[non_exhaustive].
ifc-validate
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a build naming onlyifc4no longer carries the other four: an IFC4-only browser module with validation shrinks from 1,518,692 to 926,023 bytes. The default build links every release, as before.ifc4alone used to link every release too; it now links IFC4's, andvalidate_declaredrefuses a file declaring another release withUnbundledSchema. Under 0.x, a minor release. validate_declaredis no longer gated on theifc4feature: it exists in every build and refuses a release the build does not bundle.
openbim-ifc
Changed (breaking)
propertiesre-exportsifc-properties0.4.0, whose breaking changes (PropertyAnomalyis#[non_exhaustive];template_of_setreturns every template of a set) pass through. It also resolves quantity sets inexact_propertyinstead of reporting them absent (#66).
Changed
- STEP models load lazily:
from_step_bytes,read_pathand every strict read validate the whole file but decode each entity on first access (ADR 0015, seeifc-step).read_pathhands its buffer to the codec instead of letting it copy the file once more.
Added
StepReader,ParseOptionsandOnMalformedare re-exported, so the eager and memory-mapped reads (StepReader::eager,StepReader::read_path_mapped) are reachable through the facade.
Added
Transaction,AppliedandConflictare re-exported.EntityEditorand the domain writers stage into aTransaction, which facade users could not name, so an editor could be built but never applied.
[0.5.3] - 2026-09-28
ifc-properties
Added
exact_material_property,exact_material_properties_whereandexact_material_property_sets_where(#218): exact readers for the property sets of a material definition, asexact_propertyand its enumerations read an object's. IFC4 and IFC4X3IfcMaterialProperties(the inverseHasProperties) and IFC2X3IfcExtendedMaterialPropertiesresolve their named properties; an IFC2X3 typed subtype (IfcGeneralMaterialProperties, ...) resolves its attributes as a predefined set does, keyed by its entity name. Results carry the newExactSource::Material; a material without sets is a proven absence, and a target that is no material definition of the release isInvalidQueryObject.
[0.5.2] - 2026-09-28
ifc-properties
Added
*_with_owner_historyvariants of the predefined property-set writers (#202):add_door_lining_properties_with_owner_history,add_window_lining_properties_with_owner_history,add_door_panel_properties_with_owner_history,add_window_panel_properties_with_owner_history,add_permeable_covering_properties_with_owner_historyandadd_reinforcement_definition_properties_with_owner_history. Each takes the model and a caller-suppliedIfcOwnerHistory, which IFC2X3 requires on everyIfcRoot, validated as for #191 (MissingEntity,AuthoringInvalid); none is ever invented. The record is laid out by attribute name from the declared release's table, and each value is checked against the type that release declares for it. In IFC2X3 that refusesLiningToPanelOffsetX/Y(and a window'sLiningOffset) withAuthoringNotInSchema, and a zero thickness, which IFC2X3 types asIfcPositiveLengthMeasurewhere IFC4 hasIfcNonNegativeLengthMeasure, withAuthoringInvalid; a token outside the release's enumeration is refused too. In IFC4 and IFC4X3 the record is the old writer's with the owner history in its optional slot.*_with_owner_historyvariants of the template writers (#202):add_property_set_template_with_owner_history,add_complex_property_template_with_owner_historyandattach_template_with_owner_history. They bind the model's release, so an IFC2X3 model, which declares no templates, is refused withEntityNotInSchema.ExactValue::ComplexwithExactComplexValueandExactComplexMember(#208):exact_propertyand the enumerations resolve anIfcComplexPropertyorIfcPhysicalComplexQuantityas a present composite in IFC2X3, IFC4 and IFC4X3, with no value type, instead of refusing it withUnsupportedProperty. Members resolve as set members do, nested complexes included. New errorsComplexCycle,ComplexTooDeepandComplexBudgetExceededrefuse a cycle, nesting past 16 levels and more than 10 000 nested members; a repeated member name isInconsistentValueswhere the release forbids it (WR22,UniqueQuantityNames).
Changed
- The predefined property-set and template writers that take no model (
add_door_lining_properties, ...,add_reinforcement_definition_properties,add_property_set_template,add_complex_property_template,attach_template) are unchanged: they write the IFC4 layout withOwnerHistory$, which is valid IFC4 and IFC4X3 (whose layouts of these entities are the same) and never valid IFC2X3. Without a model they cannot refuse IFC2X3; their documentation now says so, as #191 did foradd_property_set. They now lay the IFC4 record out by attribute name from the IFC4 table instead of fixed slots; the output is identical. The lining writers moved topset/lining.rsandadd_complex_property_templatetopset/template_authoring.rs; public paths are unchanged.
[0.5.1] - 2026-10-20
ifc-georef
Added
ProjectToMap::declared_map_unit(): the targetIfcProjectedCRS.MapUnitexactly as authored,Nonewhen the file leaves it unset, next to the resolvedProjectToMap::map_unit, which keeps the project length unit as the default for an omittedMapUnitand is what the transform uses. A CRS stating the project's unit is now distinguishable from one stating none, under IFC4 and IFC4X3, with or without thetransformfeature (#296).
Changed
- Documented that
ProjectedCrs::map_unitis the declaredMapUnit(Nonewhen unset), never filled in with the project default (#296).
ifc-properties
Added
add_property_set_with_owner_history,add_element_quantity_with_owner_history,attach_property_set_with_owner_historyandattach_type_with_owner_history(#191). Each takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and must be anIfcOwnerHistory; a missing one is refused withMissingEntity, another entity withAuthoringInvalid. None is ever invented. The record is laid out by attribute name from the declared release's table, and in IFC4 and IFC4X3 the reference is written into the optional slot. This followsifc-material'sassociate_material_with_owner_history(#77).PropertyError::AuthoringRequired { entity, attribute, schema }: the model's release requires an attribute the call leaves unset (#191).PropertyErroris#[non_exhaustive], so this is not breaking.
Changed
- Type objects are no longer refused by the exact API (#193).
exact_property,exact_properties,exact_properties_where,exact_property_sets_whereandexact_predefined_setsanswered anIfcTypeObjectwithExactPropertyError::InvalidQueryObject, so a checker could not test anIfcWallTypeas IDS does. They now resolve the type object's ownHasPropertySets: property sets, quantity sets and predefined sets, in IFC2X3, IFC4 and IFC4X3. Every subtype of the release'sIfcTypeObjectis accepted, IFC2X3IfcDoorStyleandIfcWindowStyleincluded. The list is read and validated by the code that already reads an occurrence's inherited type sets. A result carriesExactSource::Type(id)with the queried object's id, which is what an occurrence of that type reports for the same set.HasPropertySets = $is a proven absence;()is refused withMalformedAggregate, and duplicate names withDuplicateMatchingSets { source: Type(id), .. }andDuplicateMatchingProperties, as for an inherited type. A type object named inIfcRelDefinesByProperties.RelatedObjectsis still refused withInvalidOccurrenceTarget, also when it is the queried object: IFC2X3 admits onlyIfcObjectthere, and IFC4 and IFC4X3 forbid it byNoRelatedTypeObject("handled through the direct relationship HasPropertySets at IfcTypeObject"). Occurrence results are unchanged. No type or signature changes; callers that relied on the refusal now get answers, andInvalidQueryObjectremains for what is neither an occurrence nor a type object. Inopenbim-ifc,door_operationandwindow_operationgiven a type object now refuse withNotADoororNotAWindowinstead ofProperty(InvalidQueryObject).
Fixed
- Quantity values are written bare (#190).
create_quantity,create_quantity_withandset_quantity_valuewroteIFCQUANTITYAREA('A',$,$,IFCAREAMEASURE(12.5),$). But<Kind>Valueis declared with a defined measure type (IfcAreaMeasure, ...), not a SELECT, in IFC2X3, IFC4 and IFC4X3, and ISO 10303-21 writes a typed parameter only in a SELECT slot. They now write12.5(a whole count as the integer4).set_quantity_valuereplaces a typed value it finds with the bare one. The readers (quantity_set,exact_property) still accept both forms. Code that inspects the writtenValueseesValue::Real/Value::Integerwhere it sawValue::Typed. IfcRoot.OwnerHistoryis no longer written as$into an IFC2X3 model byattach_property_setandattach_type(#191). It is mandatory in IFC2X3 (OPTIONALfrom IFC4 on), so those records were invalid. Behaviour change for IFC2X3 callers: both now refuse an IFC2X3 model withPropertyError::AuthoringRequired { attribute: "OwnerHistory", .. }and stage nothing; use the*_with_owner_historyvariants there. Both now bind the model's declared release the way quantity authoring does. So a model whose header declares several schemas, or one without a bundled table, is refused withMultipleSchemasorUnsupportedSchemawhere it used to be written in the IFC4 layout.NoRelatedTypeObjectand theIfcTypeObjectchecks use the declared release's inheritance instead of IFC4's. IFC4 and IFC4X3 output is unchanged.add_property_setandadd_element_quantitytake no model, so they cannot see the release. They still write$and are documented as IFC4/IFC4X3 only. In IFC2X3,attach_property_set_with_owner_historyrefuses to attach a definition whoseOwnerHistoryis unset. Not changed: the template writers, whose entities IFC2X3 does not declare, and the predefined property-set writers, which write the IFC4 layout without a model.
ifc-spatial
Documentation
contain,contain_with_owner_history,serve_buildingsandserve_buildings_with_owner_historyare documented as the canonical writers; theifc-systemsduplicates are deprecated (#492).
Semver: patch. Documentation only.
ifc-style
Fixed (#514)
Semver: patch. No public item changes.
FillAreaStyle::fill_stylesaccepts a solid colour fill. It listed theIfcColourselect itself as a member ofIfcFillStyleSelect, and a select name never matches an instance's entity type, so everyIfcColourRgb(andIfcDraughtingPreDefinedColour) fill was refused withStyleError::ReferenceType. It now listsIfcColour's own members,IfcColourSpecificationandIfcPreDefinedColour, as every bundled release declares them; the colour then reads throughStyleView::colour_rgb. A non-colour member is still refused.
[0.5.0] - 2026-10-13
ifc-alignment
Added
lower_horizontal_planlowers a wholeIfcAlignmentHorizontalto one exactCurve2::Intrinsic: the first segment's start frame and aCurvatureLaw::Piecewisewith one piece per segment.HorizontalPlancarries it with its sources and seams (#92).HorizontalSeamandSeamCheckreport every seam between horizontal segments and whether its position was verified in closed form or accepted as authored after a transition spiral.LoweredAlignmentCurve::seamscarries them for layout and gradient lowerings (#239).AlignmentView::hierarchyreturns anAlignmentHierarchy: the horizontal, vertical and cant layouts and theIfcReferents anIfcAlignmentnests (IfcRelNests, in nesting order), its parent and child alignments (IfcRelAggregates), and the products it positions (IfcRelPositions).sole_horizontal,sole_verticalandsole_cantrefuse several layouts of a kind instead of picking one;governing_horizontalfollows a child alignment to the horizontal layout its parent nests;layout_segmentslists a layout's parameter segments;alignmentsandmodelround out the view (#238).VerticalLayout::resolve, the vertical counterpart ofCantLayout::resolve: the ordered segments of anIfcAlignmentVertical, refusing a gap or overlap (#238).Stationing::resolvescopes station equations to one alignment, from the referents it nests or positions, and checks eachIncomingStation(or a plain continuation) against the station carried from the previous referent withinSTATION_TOLERANCE.station_at,distance_atanddistances_atmap between station and distance along across equations and decreasing stationing; a station that occurs twice isAlignmentError::AmbiguousStation, one outside the tableAlignmentError::OutOfRange(#240).The IFC4X3 ADD2 declaration and slot inventory is pinned by tests against the bundled schema table: every slot and arity the readers and authoring index, the declarations read, the subtype and SELECT memberships relied on, every segment
PredefinedTypemember, and the absence of the layouts from IFC2X3 through IFC4X2 (#16).vertical_profile_law(model, vertical, units): the exact profile of anIfcAlignmentVertical, with its seams checked at the precision the file declares (#141).SeamToleranceandprofile_law_within: the seam tolerance as a value.SeamTolerance::for_modelreadsPrecisionfrom the model's 3DIfcGeometricRepresentationContexts (the coarsest, converted from the project length unit, capped at 1 mm);profile_lawkeeps the rounding-only rule (#141).CantLayout::for_alignment: the alignment's sole cant layout; none or several are aSemanticViolation(#93).CantLayout::frame_at_distanceandCantFrame: cant at a station as rail heights, cantD = left - right, bank anglearcsin(D / b), the rotation-point elevation(left + right) / 2and the section frame;CantFrame::orientplaces it on a caller-evaluated point and tangent (#93).lower_segmented_reference_curve: the cant-carrying centreline (IfcSegmentedReferenceCurverole). It resolves the cant layout and then refuses withUnsupported, because the pinned neutral curve vocabulary has no roll law (#93).VerticalLayout::seamslists every seam between vertical segments of positive length as aVerticalSeam(previous and next segment, station, incoming and outgoing grade) with aVerticalSeamKind:TangentialorGradeBreak.VerticalLayout::require_tangentialrefuses the first grade break withProfileDiscontinuity { seam: ProfileSeam::Gradient }for a consumer that needs a tangent profile (#259).real_export_survey(ignored test): runs every alignment path over a local directory of real IFC4X3 exports (IFC_ALIGNMENT_EXPORTS) and tallies acceptances and refusals by cause.
Changed
lower_gradient_curveandgradient_curve3elevate a multi-segment plan instead of refusing it, usinglower_horizontal_plan. The plan is now always that intrinsic curve, also for a single segment, where it used to be the segment's basis line, circle or spiral. A heading kink between segments is refused, since one intrinsic curve cannot carry it (#92).lower_horizontal_layoutlowers a layout containing transition spirals to one composite instead of refusing it. A seam after a spiral is accepted as authored, reported inseams, and declaresTransition::Discontinuous(no claim); a gap after a line or arc is still refused.lower_horizontal_layout_partialno longer splits runs at spirals, soPartialHorizontalLayout::is_completeagain means one run (#239).A
CUBIChorizontal segment is refused with its own reason: its end on the curve inverts a non-elementary arc-length integral, and the pinned neutral vocabulary has no arc-length trim. ACUBICwith a non-positive length, non-finite radii or equal start and end radii is reported asInvalidSegmentfirst (#90, still open).lower_vertical_segmentlowers throughelevation_law, like the composed gradient curve, so both paths accept and refuse the same segments.PARABOLICARCnow lowers exactly to a quadratic Bezier in the (distance along, height) plane parameterised by plan distance; it was refused (#91).Vertical
CIRCULARARCandCLOTHOIDstay a typedUnsupportedrefusal, now naming the family and why it has no polynomial law; exact lowering waits on an Axiolid elevation law (#91, #258).lower_gradient_curveandgradient_curve3check vertical seams at the model's declared precision instead of rounding only, so files whose exporter roundsStartDistAlongorStartHeightto that precision compose (buildingSMART'sBC003_*alignment reference datasets did not) (#141).A
CONSTANTGRADIENTsegment may carryRadiusOfCurvature = 0.and start and end gradients equal up to rounding, as real exports write them; both were refused.VerticalLayout::resolvechecks contiguity with the sameSeamTolerancerule asvertical_profile_law, at the model's declared precision (#141).Every layout path accepts the zero-length segment IFC4.3 requires at the end of each horizontal, vertical and cant layout (concept template Alignment Layout - Horizontal, Vertical and Cant). It adds no geometry and no curve piece; its start is still checked against the end of the previous segment by that layout's seam rule (closed-form position and heading, station and height, station and cant), and the horizontal seam is reported last in
seams. A zero-length segment anywhere else, or as a layout's only segment, is refused withSemanticViolation; the cant and vertical paths accepted one anywhere before, and the horizontal reader refused every one. Coverslower_horizontal_plan,lower_horizontal_layout,lower_horizontal_layout_partial,profile_law/profile_law_within,vertical_profile_law,VerticalLayout::resolve,CantLayout::resolveand the gradient curve (#262).read_horizontal_segmentreadsSegmentLength = 0(the schema type isIfcNonNegativeLengthMeasure); only a negative length is refused (#262).lower_horizontal_segmentandlower_vertical_segmentrefuse a zero-length segment withInvalidSegment: on its own it has no geometry. A zero-lengthCONSTANTGRADIENTlowered to a degenerate line before (#262).A grade break at a vertical seam whose height is continuous is accepted by
profile_law,profile_law_within,vertical_profile_law,VerticalLayout::resolveand the gradient curve. IFC4.3 ADD2 does not require vertical seams to be tangential (IfcAlignmentVerticalSegment), and real exports carry such breaks (buildingSMARTBC003_ALX2, Trimble). The piecewise elevation law carries the kink exactly: each piece keeps its own grade. A height step beyondSeamToleranceis still refused, andProfileSeam::Gradientis now produced only byrequire_tangential(#259).
Deprecated
station_equations, which merges the referents of every alignment in the model into one table. It keeps working; useStationing::resolve(#240).
Fixed
- A clockwise
CIRCULARARCplan elevated throughlower_gradient_curveran backwards: the evaluator reads a circle plan's distance counter-clockwise, so a negative radius was traversed in reverse. The intrinsic plan carries the signed curvature instead. lower_gradient_curveandgradient_curve3read the vertical profile at its own stations.StartDistAlongis measured from the start of the horizontal layout, but the profile was read from plan distance 0, so a profile starting at stationsput every heightsmetres early. A profile starting before the plan is now re-indexed exactly (Taylor shift of the straddling piece); one starting after the plan start, or ending before the plan ends (beyond the seam tolerance), is refused withUnsupported, because the composed curve has no domain to leave those stations without heights.
ifc-geometry
Changed
MaterialProfileSetUsageGeometry::newacceptsIfcMaterialProfileSetUsageTapering, the schema subtype ofIfcMaterialProfileSetUsage, whose inherited slots it reads unchanged; the newMaterialProfileSetUsageGeometry::tapering()returns itsMaterialProfileSetUsageTaperingGeometry(end profile set and end cardinal point), orNonefor a plain usage (#136).
Changed (breaking)
ViolationKind,SupportandFunctionStatusare#[non_exhaustive]: a match needs a wildcard arm.RuleViolation,LoweredGeometryandMappedInstanceare#[non_exhaustive]; they can no longer be built with a struct literal outside the crate.authoring::surface_curve_swept_area_solid,authoring::fixed_reference_swept_area_solidandauthoring::swept_disk_solidare removed (#210). Without the model they could not write every release correctly: the two directrix sweeps wrote the IFC4X3IFCPARAMETERVALUE(..)trim into IFC4 files, and the swept disk wrote$for the trim IFC2X3 requires. Usesurface_curve_swept_area_solid_in,fixed_reference_swept_area_solid_inand the newswept_disk_solid_in, which take&Modelafter the transaction and otherwise the same arguments.SurfaceCurveSweptAreaSolid::start_param/end_paramandFixedReferenceSweptAreaSolid::start_param/end_paramreturnGeometryResult<Option<TrimMeasure>>instead ofOption<f64>(#210). In IFC4X3 the trim is anIfcCurveMeasureSelect, and anIFCLENGTHMEASURE(..)trim is a distance along the directrix, not a curve parameter; the reader now says which (TrimMeasure::ParameterorTrimMeasure::Length) instead of returning both as a parameter. A bare number is a parameter, as IFC2X3 and IFC4 declare. A typed value that is not one of the SELECT's members is refused withWrongValueKindinstead of being unwrapped.- Lowering an
IfcSurfaceCurveSweptAreaSolidorIfcFixedReferenceSweptAreaSolidwhose trim is anIfcLengthMeasurefails withUnsupported(#210). It used to pass the length on as a curve parameter, which on a conic directrix reads metres as radians; converting a length into the directrix's parameter needs arc-length evaluation, which lowering does not do.
Added
authoring::swept_disk_solid_in(#210): anIfcSweptDiskSolidin the model's declared release. The trim is written bare in every release, as before; in IFC2X3, which declaresStartParamandEndParamrequired, an unset one is refused withInvalidAuthoredValueand nothing is staged.solid::swept::TrimMeasure(re-exported fromsolid), the kind and value of a directrix sweep's trim, andTrimMeasure::parameter.
Fixed
authoring::curve_segmentwrites aCurveMeasure::LengthasIFCLENGTHMEASURE(..)(#210). It wroteIFCNONNEGATIVELENGTHMEASURE(..), which is not a member of IFC4X3IfcCurveMeasureSelect = SELECT (IfcLengthMeasure, IfcParameterValue)and is anifc-validateerror. BecauseIfcLengthMeasureis signed and neither slot is bounded, a negative length is now written as given instead of refused.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-georef
Added
site_referencereadsIfcSite.RefLatitude,RefLongitudeandRefElevation(metres, through the project length scale) under IFC2X3, IFC4 and IFC4X3 intoSiteReference; compound angles are validated against the declared release's own WHERE rules and the WGS84 range, and convert to decimal degrees viaCompoundPlaneAngle(#242).relate_site_elevationcomparesRefElevationwith the map height of the site origin (OrthogonalHeightplus the scaled site-origin height) and returnsSiteElevationCheck: a disagreement beyond the tolerance (SITE_ELEVATION_TOLERANCE_M, 1 cm suggested) is a reported finding carrying both values and the target CRS'sVerticalDatum, never a silent pick (#242).GeorefError::InvalidCompoundAngleandGeorefError::InvalidParameter(#242).- IFC4X3
IfcRigidOperationwithIfcLengthMeasurecoordinates resolves to a translation throughresolve_project_to_map(_in): the offsets andHeightare in the targetMapUnit, and the linear part is the identity in metres (#241).WHERE SameCoordinateTypeis checked; an untyped REAL, mixed or non-length/non-angle coordinates are refused withGeorefError::RuleViolation { rule: "SameCoordinateType" }, and a length operation onto a non-projected target withWrongType. resolve_geographic_offset_inandGeographicOffset: an IFC4X3 plane-angleIfcRigidOperationread as authored onto itsIfcGeographicCRS(#241). The project-to-map resolver refuses that form with the newGeorefError::CoordinateMeasureMismatch, since no metre transform expresses a latitude/longitude offset.GeographicCrsandAngleUnit: IFC4X3IfcGeographicCRSwithPrimeMeridian,AngleUnit(PLANEANGLEUNIT) andHeightUnit(LENGTHUNIT); an absent unit staysNone(#241).create_angular_rigid_operation, writingIFCPLANEANGLEMEASURE(..)coordinates (#241).OperationSource,resolve_operation_sourceandcoordinate_operation_for: the operation'sSourceCRSvalidated as anIfcCoordinateReferenceSystemSelectmember of the pinned release, and theHasCoordinateOperationinverse read from a context or CRS (#101).ProjectToMap.source,.operationand.kind(OperationKind).- A default
transformfeature carrying theaxiolid-coredependency (#268). Withdefault-features = falsethe crate links no geometry crate and still resolves CRS metadata and units, every map-conversion and rigid-operation parameter, true and grid north, operation sources and the site reference.tests/kernel_free_build.rsasserts the resolved dependency graph, and the gate builds, tests, lints and documents the column. ProjectToMap.eastings,.northingsand.orthogonal_height, as authored inmap_unit(a rigid operation'sFirstCoordinate,SecondCoordinateandHeight,0.0when unstated), and the plain-number operation:ProjectToMap::map_point(project metres to map metres, bit-identical totransform.transform_point3),ProjectToMap::linear_partandProjectToMap::translation(#268).
Changed
ProjectToMap.transformandcompose_project_frameexist only with thetransformfeature, which is on by default, so a default build is source-compatible (#268).relate_site_elevationcomputes throughProjectToMap::map_pointand is available in both columns, with the same results.IfcMapConversionScaledresolves instead of being refused withUnsupportedOperation:FactorX/Y/Zscale the source axes before the rotation, and a non-positive factor is refused withInvalidAttribute(#241).grid_north_directionaccounts for unequalFactorX/FactorY.create_rigid_operationwrites its coordinates asIFCLENGTHMEASURE(..)instead of bare REALs, which cannot satisfySameCoordinateType(#241).- Every resolved operation now validates its
SourceCRS(#101): a dangling reference isMissingEntity, a wrong typeWrongType, a sub-contextRuleViolation { rule: "NoCoordOperation" }, and a source named by more than one operationRuleViolationon theHasCoordinateOperation : SET [0:1]inverse. Files that resolved with such a source before are now refused. resolve_project_to_mappins the header's release when it names IFC4 or IFC4X3, so IFC4X3-only entities in an IFC4 file are refused as undeclared by both entry points. A non-coordinate-operation id isWrongType { expected: "IFCCOORDINATEOPERATION" }(was"IFCMAPCONVERSION").create_map_conversionandcreate_map_conversion_scaledrefuse a negativeScale, andcreate_map_conversion_scaleda negativeFactorX/FactorY/FactorZ, withAuthoringInvalidnaming the attribute and nothing staged (#254). Zero and non-finite values were already refused. The reader refuses all of these (InvalidScale,InvalidAttribute), so a caller that passed one got a record this crate could not read back; such calls now fail at authoring time.
ifc-material
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with materials shrinks from 1,445,151 to 852,448 bytes. The default build links every release, as before. - A release the build leaves out is refused with
UnsupportedSchemainstead of panicking; so is the IFC4 baseline of a model with no header in a build without IFC4. A build without default features therefore has to name the releases it reads.
Under 0.x, a minor release.
ifc-properties
Changed (breaking)
quantity_set,quantity_setsand complex quantities list a simple quantity whose value is$, cut off by a truncated record, or not a number, as the newQuantity::Unresolved(#138). It sits in file order beside the others and carries the quantity's id,Name,Description, kind, stated unit andFormula, and areason: the newUnresolvedValue::MissingorUnresolvedValue::NotNumeric { found }. It has no value field, so nothing can read it as 0. Since #107 such a quantity was reported but left out ofquantities, so a caller listing a set could not see that it existed.PropertyAnomaly::QuantityValueMissingandQuantityValueNotNumericare still reported, once each.Quantityis#[non_exhaustive]. Exhaustive matches need an arm forUnresolvedand a wildcard arm.UnresolvedValueis#[non_exhaustive]too.comparereturnsComparison::NotComparablefor an unresolved quantity, andstated_unitreturns its stated unit.A quantity without a readable value is now checked against
WR21: a stated unit of the wrong kind is reported asPropertyAnomaly::QuantityUnitMismatch, as it is for a valued quantity.New
QuantityKind::Numberfor IFC4X3IfcQuantityNumber(#138), andQuantityKindis#[non_exhaustive]. Exhaustive matches need an arm forNumberand a wildcard arm. In a model whose declared release is IFC4X3, the permissive readers now resolveIfcQuantityNumberas aSimplequantity, where they returnedQuantity::Unsupported.NumberValueandFormulaare located by name in that release's table. A$or non-numeric value isQuantity::Unresolved, and the anomalies are reported as for the other kinds. Its measure isIfcNumericMeasure. It has no WHERE rule, so there is no unit-kind check (required_unitisNone) and a negative number is not aNegativeQuantity.comparecompares it only against a computedNumber, andstated_unitreturns whatever unit it states. IFC2X3 and IFC4 do not declare the entity. In their models, and in a model without one known declared release, it staysQuantity::Unsupported.Quantity authoring binds to the model's declared release (#138).
create_quantity(tx, model, kind, name, value)andcreate_quantity_with(tx, model, kind, name, value, extras)take the&Modelthey write into and returnResult<EntityId, PropertyError>. They used to write the IFC4 five-attribute record into every model, so an IFC2X3 quantity got aFormulaslot IFC2X3 does not declare. Attributes are now placed by name in the release's table: IFC2X3 quantities have four attributes, IFC4 and IFC4X3 ones five. The binding is the oneifc-materialuses. One recognisedFILE_SCHEMAbinds that release, and a model with noFILE_SCHEMAbinds IFC4. Several declarations are refused with the newPropertyError::MultipleSchemas, and an unknown one withUnsupportedSchema. Also refused:QuantityKind::Numberoutside IFC4X3, withEntityNotInSchema;- a
Formulafor an IFC2X3 model, withAuthoringNotInSchema; - a non-finite value, with
AuthoringInvalid.
A count is no longer truncated. A whole count is still written as an integer. A fractional one is written as a real where
IfcCountMeasureisNUMBER(IFC2X3, IFC4), and refused where it isINTEGER(IFC4X3).set_quantity_valuebinds the same way. It writes the value slot named in the release, refuses a record whose attribute count is not the release's withMalformedEntitySlots, and repairs a$value (aQuantity::Unresolved) into a simple quantity. Nothing is staged on a refusal. The newPropertyErrorvariants are additive, since the enum is#[non_exhaustive]; the signatures are the breaking part.add_quantity_to_set,add_element_quantityandadd_physical_complex_quantityare unchanged, becauseIfcElementQuantityandIfcPhysicalComplexQuantityhave the same attributes in all three releases.
The exact API is unchanged and agrees: exact_property refuses a $ value with MissingValueSlot, a non-numeric one with UnsupportedValue and a truncated record with MalformedEntitySlots, in IFC2X3, IFC4 and IFC4X3.
PropertyTemplatereads every attribute of both template entities by name from the declared release's table (#108) and gainskind(PropertyTemplateKind::SimpleorComplex),enumerators,secondary_unit,expression,access_state,usage_nameandtemplates(the nestedHasPropertyTemplatesof a complex template, read through the same bounded, cycle-aware traversal as complex properties, withtemplate(name)to look one up). It is now#[non_exhaustive], so code that builds it with a struct literal or destructures it exhaustively must change; later fields will not break callers again.
Added
exact_property_sets_where(model, object, select_set)andExactPropertySetEntry { name, set_id, source, members }: the property sets and quantity sets an object carries whose names the selector picks, empty ones included (#186).exact_properties_wherelists properties, so a matching set without any left no trace; an IDS property facet must fail on exactly that set. The traversal, type-over-occurrence order and refusals are those ofexact_properties_where, except that aHasPropertiesorQuantitiesof()or$(invalid, both areSET [1:?]) is listed withmembers == 0instead of refused: the question is whether the set exists. A type set with an occurrence set's name is listed as well, since overriding works per property.template_deviations(model)compares every property set that anIfcRelDefinesByTemplatelinks to anIfcPropertySetTemplatewith that template (#109), reading the model and its templates only, bound to the declared release (IFC4 or IFC4X3; IFC2X3 has no templates and is refused withTemplateError::NoTemplates). It returns aTemplateReportofTemplateFindings, each naming the set, the template and the concrete mismatch:MissingProperty,UnexpectedProperty(matched byName, as the IFC4IfcPropertySetTemplatedocumentation states),WrongForm(the property's entity against the template'sTemplateType, e.g. a single value whereP_ENUMERATEDVALUEis prescribed),WrongMeasureType(PrimaryMeasureType, andSecondaryMeasureTypefor bounded and table values, against the declared type of each value or the referenced entity),WrongSetKind(aQTO_*template on anIfcPropertySet, aPSET_*one on anIfcElementQuantity),WrongAttachment(a*_TYPEDRIVENONLYset on an occurrence, an*_OCCURRENCEDRIVENset on a type) andOutsideApplicableEntity(IfcEntity[/PREDEFINEDTYPE]entries, comma separated). Complex properties are compared with complex templates member by member. Quantity sets are checked the same way withQ_*templates. What the documentation leaves open isTemplateFinding::Undecidedwith anUndecidedReason, never guessed: an unknown or undocumented template type (IFC4X3Q_NUMBER), an unknown measure type orApplicableEntityentry, an object whose predefined type is unstated, a[PerformanceHistory]entry, a predefined property set. Malformed facts met on the way arePropertyAnomalys in the same report. New types:TemplateReport,TemplateFinding,MeasureRole,UndecidedReason,TemplateError, all#[non_exhaustive].property_template_checkedandproperty_set_template_checkedread a template bound to the declared release and report every malformed fact met (#108), refusing a model without a single supported release, an IFC2X3 model (TemplateError::NoTemplates), an absent entity and a non-template withTemplateError.PropertyAnomaly::NotATemplate,PropertyAnomaly::SlotCountMismatchandPropertyAnomaly::MalformedAttribute(#108, #109): a template member orRelatingTemplatethat is no template, a record whose attribute count is not the release's, and an attribute value its declared type does not admit (including an enumeration constant the release does not define).PropertyAnomalyis#[non_exhaustive], so this is not breaking.
Fixed
property_templateread every template with theIfcSimplePropertyTemplatelayout (#108), so anIfcComplexPropertyTemplate, including one written byadd_complex_property_template, reported itsUsageNameas itsTemplateTypeand never exposed its nested templates; and any entity at all read as a template. It now returnsNonefor an entity that is not a simple or complex template in the declared release (the IFC4 table when the header names none it bundles, and nothing in an IFC2X3 file).property_set_template,property_set_templatesandtemplate_of_setread their attributes by name the same way.
ifc-spatial
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table. The default build links every release, as before. Container classification needs IFC2X3, IFC4 or IFC4X3: a build naming none of them would classify nothing as a container, so it fails to compile with a message naming the features. A build without default features therefore has to name a release; under 0.x, a minor release. - A file declaring a verified release the build leaves out binds none (
SpatialTree::releaseisNone) and is classified against every release the build bundles, as an IFC4X1 or IFC4X2 file already was, rather than against an empty table that would make every entity an element.
Changed
- The relationship attribute names authoring writes are constants instead of IFC4X3 table lookups, so authoring links no table; the unit test pins all 29 against the IFC4 and IFC4X3 tables.
ifc-step
Changed (breaking)
- Requires
openbim-step=0.11.0(was=0.8.0; #288).ParseOptionsandOnMalformed, re-exported here and taken byStepCodec::with_optionsandStepReader::new, are now that release's types, as is the source ofFrom<openbim_step::StepError> for StepError. Code that namesopenbim_step0.8 types alongside this crate must move to 0.11. The 0.9 owned-model changes (Str,Instance, boxed parameter lists) stay inside this crate: noopenbim-stepmodel type is part of its API.
Added
- Lenient reading (
StepCodec::lenient) accepts a REAL written without its decimal point (1E-05,-2E3,3e+2), as buildingSMART's IFC4.x alignment test files do (#285). ISO 10303-21 requires the point (REAL = [sign] digit {digit} "." {digit} [exponent]); the meaning of such a token is not in doubt, so the read takes it as the REAL it spells and reports one located model diagnostic per token instead of skipping the record. Recognising the token isopenbim-step'sParseOptions::accept_real_without_point, whichParseOptions::lenient()includes (#288); this crate only words the diagnostic. Text in strings is never touched; a token that is not a number (1E,1EE2) is still skipped as a malformed record. The writer still always emits the point. ParseOptions::strict().accept_real_without_point(true)keeps the abort policy and reads lazily: each record is validated under the read's options and its diagnostics are added in file order, exactly as the eager read adds them.ParseOptions::strict().on_malformed_record(Skip)does not accept such tokens; it skips their records, as 0.4.0 did.StepError::RealWithoutDecimalPoint { offset, token }: a strict read (lazy or eager, andIndex::entity) refuses such a token as before, now with an error naming it; throughCodecit surfaces asModelError::Syntaxat the token's offset with the token in the detail.StepErroris#[non_exhaustive], so this is not breaking.
ifc-style
Added (#449)
StyleIndex: everyIfcStyledItem,IfcPresentationLayerAssignmentandIfcMaterialDefinitionRepresentationof a model indexed once, withitem_colour(item)(the item's ownIfcStyledItem, then its styled presentation layers) andmaterial_colour(material)(the item-lessIfcStyledItems of the material'sIfcStyledRepresentations). Both return aSurfaceColour: RGB, transparency (Nonewhen unstated), theIfcSurfaceStyleand aColourSource(StyledItem,PresentationLayer,Material). The precedence, item over layer over material, is documented and cited (IFC4 ADD2 TC1IfcPresentationLayerWithStyle,IfcStyledItem,IfcMaterialDefinitionRepresentation). It is applied per colour: a level that states no surface colour does not hide the next. IFC2X3 reads itsIfcPresentationStyleAssignmentwrappers, and its transparency fromIfcSurfaceStyleRendering.StyleError::AmbiguousSurfaceColour: one level stating two different surface colours (two surface styles in one styled item, two styled layers, a material whose styled representations disagree).
resolve_item_style is unchanged.
Semver: additive (StyleError is #[non_exhaustive]), a minor release while 0.x.
ifc-systems
Deprecated
contain_in_spatial_structureandcontain_in_spatial_structure_with_owner_historyin favour ofifc_spatial::containandifc_spatial::contain_with_owner_history, andserve_buildingsandserve_buildings_with_owner_historyin favour of theifc_spatialfunctions of the same names (#492). Each pair wrote the same relationship with the same rules;ifc-spatialkeeps the canonical writer. The deprecated functions keep their bodies (sibling domain crates do not depend on one another) and behave as before.
Semver: minor. A deprecation is a compatible change: callers get a warning, not an error.
ifc-validate
Added
Native WHERE rules over property sets and type assignments (#215), registered only for the releases whose EXPRESS states them and checked against it by
tests/registry_scope.rs:IfcObject.UniquePropertySetNames(IFC4, IFC4X3):IfcUniqueDefinitionNames(IsDefinedBy), with the inverse rebuilt from everyIfcRelDefinesByPropertiesand anIfcPropertySetDefinitionSetopened;IfcTypeObject.UniquePropertySetNames(IFC4, IFC4X3):IfcUniquePropertySetNames(HasPropertySets);IfcTypeProduct.ApplicableOccurrence(IFC4, IFC4X3) and its IFC2X3 labelIfcTypeProduct.WR41: every object a type product is assigned to byIfcRelDefinesByTypeis anIfcProduct.
One finding per shared name; a definition the file lacks is an evaluation error. IFC2X3 states no unique-set-name rule. The textual
IfcTypeObject.ApplicableOccurrenceandIfcPropertySetTemplate.ApplicableEntityare stated as a rule by no release and are not checked.type_check::check_value_all: every independent mismatch of one value against one declared type.
Changed
type_check::attribute_typesreports every independent violation in a slot instead of the first (#215): each bad member of an aggregate, at every nesting level, and a wrapper's form together with a bad parameter inside it (IFCLABEL(12)in anIfcLabelslot is now bothtype.typed.outside_selectandtype.scalar.mismatch; a wrapper outside its SELECT istype.select.memberand its parameter is still judged). Identical mismatches in one slot are reported once. A report on a malformed file can therefore hold more findings than before.type_check::check_valuestill returns the first.
openbim-ifc
Added
ifc::properties::exact_unit(ifc-properties 0.3): a measure's effective unit resolved exactly to SI, or refused (#53).ifc::spatial::SpatialTree::anomalies(ifc-spatial 0.2.1): double containment and double aggregation are reported (#54).
Changed
- Breaking: re-exports ifc-properties 0.3, whose
UnitKind::Si::prefix_exponentis nowOption<i32>and whoseUnitKind::Conversiongains anoffsetfield.
[0.4.4] - 2026-09-28
ifc-geometry
Added
authoring::surface_curve_swept_area_solid_inandauthoring::fixed_reference_swept_area_solid_in(#200). They take the model and writeStartParam/EndParamin the form its declared release requires: bare in IFC2X3 and IFC4, where the slot isIfcParameterValue, andIFCPARAMETERVALUE(..)in IFC4X3, where it is the SELECTIfcCurveMeasureSelect. The release binds fromFILE_SCHEMAas the other authoring crates bind it (none binds IFC4). An attribute the release requires left unset (IFC2X3Position,StartParam,EndParam) is refused withInvalidAuthoredValue.GeometryError::AuthoringSchemaUnbound(an unknown or ambiguousFILE_SCHEMA) andGeometryError::AuthoringEntityNotInSchema(the release does not declare the entity, such as the fixed-reference sweep in IFC2X3), for those writers.GeometryErroris#[non_exhaustive], so this is not breaking.authoring::grid_with_owner_history(#202): anIfcGridin the model's declared release, with a caller-suppliedIfcOwnerHistory, which IFC2X3 requires on everyIfcRoot. It binds the release as the_inwriters above do, and lays the record out by attribute name from its table, so an IFC2X3 grid has its 10 attributes, not IFC4's 11. Apredefined_typein IFC2X3, which declares none, or outside the release'sIfcGridTypeEnum, is refused withInvalidAuthoredValue. The owner history must be in the model or staged on the transaction and be anIfcOwnerHistory(InvalidAuthoredValueonOwnerHistoryotherwise); none is ever invented. IFC4 and IFC4X3 records aregrid's with the owner history in its optional slot. No error variant is added.
Fixed
IfcParameterValueslots are written bare (#200):rectangular_trimmed_surface(U1,V1,U2,V2),point_on_curve,point_on_surface,reparametrised_composite_curve_segment(ParamLength), and theStartParam/EndParamofswept_disk_solidandswept_disk_solid_polygonal. Each is declared with the defined typeIfcParameterValue, not a SELECT, in every release that declares it, and ISO 10303-21 writes a typed parameter only for a SELECT. The readers accept both forms, as before.
Changed
surface_curve_swept_area_solidandfixed_reference_swept_area_solidstill writeIFCPARAMETERVALUE(..), which is correct in IFC4X3 only. They cannot see the release; their docs now say so and point IFC4 (and IFC2X3) callers to the_inwriters.authoring::gridis unchanged and documents its limitation (#202): it takes no model, so it writes the IFC4 layout (11 attributes,OwnerHistory$), which is never valid IFC2X3. It moved fromauthoring/transform.rstoauthoring/grid.rs; the public path is the same.
[0.4.3] - 2026-09-28
ifc-geometry
Added
BodyItem::item_world: the frame each described item is placed in, the context and product placement composed with everyMappingTarget o MappingOriginit was reached through (#185). It is reported for every item kind, including mapped B-reps and tessellations, and is not required to be rigid, so a mapping that mirrors or scales shows.BodyItem::is_mirrored()answers whether that frame reverses handedness, andTransform::determinant()gives its signed volume scale. Additive:BodyItemis#[non_exhaustive].
[0.4.2] - 2026-09-27
ifc-geometry
Changed
data/ifc4-where-rules.tsvlists each geometry WHERE rule by entity, label and support state only. Itsexpressioncolumn held the rule bodies verbatim, which are CC BY-ND schema text and are no longer shipped.data/NOTICE.mdcovers all five data files and no longer claims the directory holds no rule bodies while it did.
[0.4.1] - 2026-10-03
ifc-geometry
Fixed
- An
IfcCurveBoundedPlanelowered under a non-identity frame no longer moves its boundaries twice (#163).OuterBoundaryandInnerBoundariesare in the basis plane's parameter space, which is also how the neutral curve-bounded relation reads them, but the frame was applied to them as well as to the plane: they landed elsewhere or were refused as off the plane. Only the basis plane takes the frame now, so a framed plane meshes to the identity mesh moved by the frame. Connection surfaces lowered under a space's frame are the case this breaks.
Added
product_representation_frame(model, units, product, purpose): the frame a product's representation of that purpose is placed in, the context'sWorldCoordinateSystemcomposed above the placement chain (#164). Lowering andbody_descriptionnow take their frame from it, so geometry lowered outside the body, such as a space boundary's connection surface in the relating space's coordinates, is placed exactly as the body is.Ok(None)when the product has no such representation; kernel-free.profile_outline(model, units, profile)andProfileOutline(#166): anIfcArbitraryClosedProfileDef's orIfcArbitraryProfileDefWithVoids's boundaries as rings of vertices in metres, in profile coordinates, forIfcPolylineand line-onlyIfcIndexedPolyCurveboundaries. Each ring is in authored order without its closing vertex, as profile lowering reads it. AnIfcArcIndexsegment or any other curve family isUnsupportednaming the curve, never chorded; a 3D point, fewer than three distinct vertices and non-consecutive segments areDegenerate. Kernel-free.
Changed
lower_product_representationselects the representation before it resolves the placement, asbody_descriptionalready did: a product with no representation of the purpose isOk(None)even when its placement is broken, where it used to be the placement error.
ifc-properties
Added
exact_property,exact_propertiesandexact_properties_whereresolve the attributes of predefined property sets (#149):IfcDoorLiningProperties,IfcDoorPanelProperties,IfcWindowLiningProperties,IfcWindowPanelPropertiesand every otherIfcPropertySetDefinitionthat is neither a property set nor a quantity set, in IFC2X3, IFC4 and IFC4X3. A set's members are the attributes its entity declares belowIfcPropertySetDefinition, named and typed as the declared release's table has them (LiningThicknessisIFCPOSITIVELENGTHMEASUREin IFC2X3,IFCNONNEGATIVELENGTHMEASUREin IFC4). Values keep the provenance of a single value (property_idis the set's id) with no explicit unit, so a length resolves to the project unit throughexact_unit; ratios are their ratio measure. NewExactValue::Enumcarries an enumeration constant checked against the release's members, andExactValue::Entityan entity reference (ShapeAspectStyle) checked but not followed. An unset optional attribute isPresentwithExactValue::Nulland its declared type; an unset required one isMissingValueSlot. A set that states noNameis found under its entity name (IfcDoorLiningProperties); such sets of one entity (a door's panel set per leaf) are ambiguous only for a member they share. Newexact_predefined_sets(model, object, entity)returns every assigned set of that entity with all its attributes (ExactPredefinedSet, withattribute(name)), occurrence sets first, no override applied, for door operation geometry (#148); a name that is not a predefined set in the release is the newExactPropertyError::NotAPredefinedSet. What cannot be read exactly is still refused withUnsupportedDefinition: a selected aggregate attribute (ReinforcementSectionDefinitions), and, as before, a set withoutNamethat the set name does not select but that has an attribute of the requested name (#66). A predefined set'sNamethat is neither text nor$is nowMalformedNamerather than treated as unnamed.exact_property,exact_propertiesandexact_properties_whereresolveIfcPropertyEnumeratedValue,IfcPropertyListValue,IfcPropertyBoundedValue,IfcPropertyTableValueandIfcPropertyReferenceValue(#150), which they refused withUnsupportedProperty. NewExactValuevariantsEnumerated,List,Bounded,TableandReferencecarry them, with the new typesExactTypedValue(oneIfcValueand its declared type),ExactEnumeratedValue/ExactEnumeration(selected values and the referencedIfcPropertyEnumeration),ExactBoundedValue(lower, upper, set point),ExactTableValue/ExactTableRow(rows, expression, defining and defined unit, interpolation),ExactReferenceValueandExactEntityRef(usage name and target).ExactProperty.value_typeisNonefor these;unit_idis the list's, the bounded value's or the enumeration'sUnit. Every attribute is read by name from the declared release's table: IFC2X3 requires the value lists and the reference target and has noSetPointValueorCurveInterpolation; IFC4/IFC4X3 make them optional. A malformed kind is refused as a single value is, and the WHERE rules that decide how the values read (one type per list and between bounds, equal table columns, selected values drawn from the referenced enumeration) andLIST OF UNIQUEare enforced with the newExactPropertyError::InconsistentValues { entity, rule }, whereruleis the release's own label (WR21in IFC4,WR1in IFC2X3). Both enums are#[non_exhaustive]; callers that relied on the refusal now get answers. AnIfcComplexPropertyis still refused.exact_properties(model, object)andexact_properties_where(model, object, select_set, select_property): exact enumeration of an object's properties and simple quantities (#78), for checks such as IDS property facets that name sets and properties by pattern. EachExactPropertyEntrycarries the member's name and the sameExactProperty(provenance, set, value type, unit, value) thatexact_propertyreports. The traversal, model and assignment validation and refusals are those ofexact_property, and for one set name and one property name the result equals its answer ([x]forPresent(x), empty forAbsent, the same error otherwise). An inherited property is left out when an occurrence set of the same name selects a property of the same name. An empty result is a proven absence. Only selected members must resolve: an unselectedIfcPropertyEnumeratedValueor complex quantity does not refuse the answer, but every member of a selected set must be well formed. Additive; nothing existing changes.exact_property,exact_schemaandexact_unitresolve IFC4X3 ADD2 models (#76), bound to the bundled IFC4X3 ADD2 table. They were refused withExactPropertyError::UnsupportedSchema. The header tokens are thoseifc_schema::SchemaVersion::from_header_tokenmaps to that release (IFC4X3_ADD2andIFC4X3). Tests pin the IFC4X3 differences:IfcRelDefinesByProperties.RelatedObjectsadmits any non-typeIfcObjectDefinition(anIfcProjecttoo),IfcPropertySetDefinitionSetis traversed under itsSET [1:?]rules,IfcTypeObject.HasPropertySetsmay be$but not empty,IfcQuantityNumberresolves asIFCNUMERICMEASUREonly in IFC4X3, andIfcDerivedUnithas IFC4X3's four attributes.IfcDimensionsForSIUnit/IfcCorrectDimensionsfor IFC4X3 are checked against the EXPRESS source, so the farad keeps IFC4's dimensions. Callers that relied on IFC4X3 being refused now get answers; the result type is unchanged.PropertyAnomaly::MemberNotReferenceandPropertyAnomaly::DuplicateMember(#137).property_set_checked,property_checked,quantity_set,quantity_sets,property_sets_by_objectandresolved_propertiesreport an item ofHasProperties,QuantitiesorHasQuantitiesthat is not an entity reference, and a member listed twice, instead of skipping the one and reading the other twice.PropertyAnomalyis#[non_exhaustive], so this is not breaking.property_checkedandproperty_set_checked: the values ofpropertyandproperty_set, together with aPropertyAnomalyfor every member they could not resolve (#107).property_sets_by_objectandresolved_propertiesnow include these anomalies, once per set.PropertyAnomalyvariants for what nested and malformed members used to lose silently (#107).PropertyAnomalyis#[non_exhaustive], so this is not breaking:ComplexCycle: a complex property or complex quantity reaches itself again through its members, at any cycle length.ComplexTooDeep: complex nesting deeper than 16 levels.ComplexBudgetExceeded: one read followed more than 10,000 nested member references.MissingMember: a set or complex entity lists an id absent from the file.QuantityValueMissing/QuantityValueNotNumeric: a simple quantity whose value attribute is$, absent, or not a number.
Fixed
- A member listed twice in a property set, quantity set, complex property or complex quantity was resolved twice (#137). It is now resolved once, at its first position, and reported as
DuplicateMember; a property set therefore no longer reports such a repeat as aDuplicatePropertyNameof itself. - Complex properties and complex quantities guarded nesting only against a DIRECT self-member and a bare depth of 16 (#107). A longer cycle, or nesting past the depth, was silently truncated to an empty member list, and a densely cyclic file cost exponential work (a clique of 8 complex properties followed 7^16 paths) before the depth stopped it. Members are now followed along a tracked path with a depth bound and a member budget, and every cut is reported. A member shared by two complex properties (legal:
IfcProperty.PartOfComplexisSET [0:?]) is still resolved under both and is not a cycle. quantity_setandquantity_setsno longer drop, without a word, a simple quantity with a missing or non-numeric value, or a member id absent from the file (#107). Such members are still left out ofquantities(aQuantity::Simpleneeds a number), but each is now reported.- The
propertydocumentation claimed over-deep nesting yieldsPropertyValue::Unsupported; it never did. It now states what happens.
ifc-xml
Added
XmlCodec::xsdnow writes the buildingSMART XSD configuration too (#274). Every entity is a top-level element named as the schema spells it (IfcWall) withid="i<n>"; entity values arerefreferences withxsi:nil, andxsi:typewhere the entity is a subtype of the declared type; simple values are XML attributes in their XSD lexical form (lower case enumerations,xs:doublereals with a '.',xs:hexBinary), lists whitespace separated; SELECTs hold entity elements or-wrappervalues; aggregates are containers of items,Seq-wrapped inner lists, or flat items withifc:arraySize; the 21 attributes the configuration leaves off a relationship (IfcRelAggregates.RelatingObject) are written as references inside the inverse of the entity they name (IsDecomposedBy); the header is the XSD's. Each attribute's form comes from the derivation the reader uses, now one shared rule plus the configuration's few exceptions, and a schema-backed test checks every attribute, inverse and element order against both release XSDs. Every IFC4 fixture the writer accepts validates againstIFC4.xsdwith no error and reads back with the XSD reader to the same model (entities numbered in model order,SETs filled through inverses in document order); the STEP implementation level has no ifcXML field and is not written.XmlError::Unrepresentable: the writer refuses a model the configuration cannot carry exactly rather than write a different model or an invalid document -- an integer where a real is declared, a string with a tab or line break (the XSD types strings asxs:normalizedString) or over its declared width, a string with whitespace in a list attribute, a partial-byte binary, an unset mandatory element, an aggregate outside its declared bounds, more than one header author, organization or description,IfcRelDefinesByObject.RelatingObject(the configuration writes no inverse for it), a second relationship where the XSD allows one (HasOpenings), or an inverse the entity's XSD type restricts away (IFC4IfcOrientedEdge.StyledByItem). Models the schema does not describe are refused with the existing typed errors.
Fixed
- The XSD reader accepts the namespace-qualified
ifc:arraySize,ifc:itemTypeandifc:cTypea valid document carries: the XSD declares them as global attributes, so unqualified ones are not valid. It refused them as undeclared; unqualified ones are still read.
[0.4.0] - 2026-10-20
ifc-alignment
Fixed
read_vertical_segment,read_cant_segmentandCantLayout::resolve(RailHeadDistance) read a typed parameter such asIFCLENGTHMEASURE(1.)exactly like the bare number, asread_horizontal_segmentalready did; they refused it as the wrong kind (#140).
Changed (breaking)
- The authoring drafts
HorizontalSegmentDraft,VerticalSegmentDraftandCantSegmentDraftare#[non_exhaustive]: build them withHorizontalSegmentDraft::new(start_point, start_direction, start_radius, end_radius, segment_length, predefined_type),VerticalSegmentDraft::new(start_dist_along, horizontal_length, start_height, start_gradient, end_gradient, predefined_type)orCantSegmentDraft::new(start_dist_along, horizontal_length, start_cant_left, start_cant_right, predefined_type)and the settersgravity_center_line_height,radius_of_curvature,end_cant_leftandend_cant_right. Fields stay public. - The read-side
HorizontalSegment,CantSegment,CantLayout,CantAtStation,LinearPlacement,StationEquation,LoweredAlignmentCurve,PartialHorizontalLayoutandRefusedSegmentare#[non_exhaustive]; they can no longer be built with a struct literal outside the crate.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - Pinned by test:
AlignmentView::for_modelrefusesIFC4X1andIFC4X2(their alignment model differs from IFC4X3).
ifc-classification
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with classification shrinks from 1,420,062 to 827,350 bytes. The default build links every release, as before. - A release the build leaves out is refused with
UnsupportedSchemainstead of panicking; so is the IFC4 baseline of a model with no header in a build without IFC4. A build without default features therefore has to name the releases it reads.
Under 0.x, a minor release.
ifc-cost
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with cost shrinks from 1,395,402 to 802,733 bytes. The default build links every release, as before. - A release the build leaves out is refused with
UnsupportedSchema, as an unbundled release already was. A build without default features therefore has to name the releases it reads.
Under 0.x, a minor release.
Added
CostValue::applied_value(): theAppliedValueexactly as authored, typed wrapper and integer payload intact, andCostValue::unit_basis_component(model): the rate basis'sValueComponentlikewise.amount()andUnitBasis::valuestill give the number. For lossless consumers such as the language bindings (#123).- Additive: a patch release.
ifc-geometry
Fixed
A face surface whose
FaceSurfaceis dangling now reports the face as the referrer, and one naming a non-surface isWrongEntityTypenaming the target (#155). The first reported the missing id against itself; the second was anUnsupported"curved and B-spline surfaces", which reads as valid IFC this bridge declines rather than a broken reference. Applies to faces inside advanced B-reps too.Compiled
IfcBlockmeshes were offset by half their extents.IfcBlockhas a corner at itsPosition(IFC4 ADD2 TC1), but the neutralPrimitive::Blockis tessellated centred on its origin byaxiolid-reference, so every compiled block sat half its size away from where the file placed it, along its own axes. Lowering now puts the half-extent shift on the block'sInstance. This changes compiled geometry for everyIfcBlock; the loweredInstancetranslation now names the block's centre. The other CSG primitives already agreed.Plane,CylindricalSurface,SphericalSurfaceandToroidalSurface::position(&model)now type-check their target throughresource::resolve(#135). APositionnaming anything other than anIfcAxis2Placement3DisWrongEntityTypenaming the target; it used to be wrapped as a 3D placement and misread.A derived linear placement no longer reports an evaluator's degenerate curve as an undefined roll. The refusal now distinguishes an unsupported curve family, a rejected measure (off the curve, or roll undefined because the tangent is parallel to the up reference) and a degenerate curve, matching how
axiolid-evaluate0.3 reports them.An opening that a file makes void two hosts is subtracted from the first only (#59).
IfcFeatureElementSubtraction.VoidsElementsis a single-valued inverse in IFC2X3 and IFC4, but everyIfcRelVoidsElementwas applied, so the second host's net body was cut by an opening that belongs to another element. The relation with the lower id now wins inopenings_ofand in net compilation. Output changes only for files that violate the schema.IfcParameterizedProfileDef.Positionnow reaches the kernel for every parameterised family (#147). It was applied to rectangles and circles only, so an I, L, T, U, C or Z section, an ellipse or a trapezium with an offset or rotatedPositionlowered at the profile origin. Output changes only for files that author a non-identityPositionon those families.The translation of an
IfcDerivedProfileDefoperator is converted to metres (#147). It was passed through in file units, so a derived profile offset by 50 mm in a millimetre file lowered 50 m away.An
IfcAsymmetricIShapeProfileDefin an IFC2X3 file no longer readsCentreOfGravityInY(slot 11 in that schema) asBottomFlangeEdgeRadius(#147). IFC2X3 declares the entity as anIfcIShapeProfileDefsubtype with a different tail; the declared schema now selects the layout, and the IFC4-only edge radii and slopes are absent in IFC2X3.
Added
IfcFaceSurfaceandIfcAdvancedFacelower as representation items (#155). Both are members ofIfcSurfaceOrFaceSurface, the type of a connection surface, but the dispatcher refused them. The newlower_face_surface_nodebuilds ONE face through the B-rep face path -- exact carrier surface,SameSenseas the face's orientation relative to that carrier, each bound'sOrientation-- inside one open shell with no solid, so a single face never acquires a volume. They move from the nested-only disposition ledger toIMPLEMENTED, andBodyKind::Facedescribes them. Committed evidence:test/fixtures/synthetic-surfaces/synthetic_space_boundary_face_surface.ifc.lower_connection_surface(session, connection, frame)andlower_related_connection_surfacelower anIfcConnectionSurfaceGeometry'sSurfaceOnRelatingElementand optionalSurfaceOnRelatedElement(#155), dispatchingIfcSurface,IfcFaceSurface/IfcAdvancedFaceandIfcFaceBasedSurfaceModel. Each end is authored in its own element's coordinate system, so each takes its own frame. Point, eccentric point, curve, volume and (IFC2X3) port connections are typedUnsupportedrefusals naming the connection; any other entity isWrongEntityType.body_description(model, units, product)reports how a product's Body representation is modelled (#147). It returns oneBodyItemper geometric item in authored order, with mapped items resolved (mapped_bynames the chain), each carrying aBodyKind(extrusion, tapered extrusion, revolution, tapered revolution, directrix sweep, swept disk, sectioned spine, B-rep, CSG, CSG primitive, half space, bounding box, tessellated, surface model, geometric set, curve, surface, point) and, for the swept-area families, aSweptSolid: the profile description, the end profile of a tapered sweep, the solid's placement in world coordinates and aSweepPath(extrusion direction as a world unit vector plus depth in metres, revolution axis and angle in radians, or the directrix curve). Mapped geometry describes identically to the same geometry authored in place. Anything that cannot be stated exactly is a typed error for the whole body, never a partial list: unsupported item or profile families, dangling references, mapping cycles, open profiles swept as areas, and a mapping that scales or mirrors a swept solid. It is kernel-free and reachable with--no-default-features.describe_profile(model, units, profile)reads any concreteIfcProfileDefinto aProfileDescription: type,ProfileName,Position, andProfileParametersfor every family in metres and radians (rectangle, rounded and hollow rectangle, circle and hollow circle, ellipse, I, asymmetric I, L, T, U, C, Z, trapezium, arbitrary closed, with voids and open by curve reference, centre line, composite, derived with its operator, mirrored). A bareIfcProfileDefand unknown families are refused; a composite or derived chain that references itself isCyclicChain.derive_placement_transformderives a linear placement on anIfcPolylineor a line-onlyIfcIndexedPolyCurvebasis curve (#96), not only on an alignment. The curve lowers to the neutral polyline, whose arc length is an exact finite sum, so a distance converts to a parameter exactly; a native parameter follows the IFC polyline parameterisation (one per segment). Refused by name: a zero-length segment, fewer than two points, non-consecutiveSegments, anIfcArcIndex, a parameter on a multi-pointIfcLineIndex(IFC does not state its split), and ellipse and B-spline bases as before.product_boundsbounds linear extrusions of straight-edged profiles (rectangles, polyline contours, and placed/derived forms of them) and blocks exactly from their vertices, without tessellating (#98). The result reportsBoundsSource::Exact. Curved profiles, rounded rectangles and booleans still go through the compiled mesh; a difference only shrinks its operand, so its operand's box is never used.voiding_conflicts(model)andVoidingConflict { opening, kept_host, rejected_host, relation }report such openings (#59). It is kernel-free, likeopenings_of. Restating the same host is not a conflict.Resolving typed accessors beside the raw
*_refgetters (#97), followingPlane::position(&model):Line::point,Polyline::points,IndexedPolyCurve::points,BSplineCurve::control_points,OffsetCurve3D::ref_direction,Circle::position,Ellipse::position,Trim::cartesian_point,BSplineSurface::control_point_views,SurfaceOfLinearExtrusion::{position, extruded_direction},SurfaceOfRevolution::{position, axis_position},BoundingBox::corner_pointandTessellatedFaceSet::coordinate_list. Each type-checks its target: a dangling reference isMissingEntitynaming the referrer, a wrong type isWrongEntityTypenaming the target. All are kernel-free.resource::resolve, the shared type-checked resolvers behind them, and two select views:Axis2Placement(2D or 3D, forIfcConic.Position) andCartesianPointList(2D or 3D, forIfcIndexedPolyCurve.Points). Profiles stay references;describe_profileownsIfcProfileDefreading.compile::product_bounds/product_bounds_withreturn a product's world-space axis-aligned bounding box (#36). The body is resolved and placed the same way as forcompile_product_mesh, and the result isOk(None)in the same case (no body).- When every leaf of the lowered graph is a mesh or an authored bounding box, the box is read off the exact graph without tessellating (
BoundsSource::Exact). - Otherwise it comes from the compiled mesh (
BoundsSource::Tessellated). That box is exact for planar geometry and can fall short of a curved surface by up to the tolerance. - A body with no finite extent is
GeometryError::Degenerate, never an empty box.
- When every leaf of the lowered graph is a mesh or an authored bounding box, the box is read off the exact graph without tessellating (
examples/product_bvh.rsindexes every product of a file inaxiolid_spatial::Bvhand prints the broad-phase overlaps.axiolid-spatialis a dev-dependency only: the index stays Axiolid's, and this crate only produces the boxes.
Changed
lower::profilebuilds every profile fromdescribe_profileinstead of reading slots itself (#147), so lowering and body description cannot disagree about a slot, a unit or a default. A profile nesting chain that exceeds its budget is nowChainTooDeeprather thanUnsupported, and a self-referencing chain isCyclicChain; a dangling boundary curve of an arbitrary profile is reported when the profile is read.- Requires
axiolid-mesh-compile0.3.4,axiolid-construct0.3.3 andaxiolid-evaluate0.3.1. Compiled output changes where the kernel's did: a B-rep's void shells are tessellated facing into the cavity instead of being dropped, so an authored cavity is no longer filled (axiolid/kernel#120); every solid of a multi-solid B-rep is meshed, not only the first (axiolid/kernel#111); and a curve-bounded plane, the usual space-boundary connection surface, compiles to a planar surface mesh instead of being refused (axiolid/kernel#192). - Requires
axiolid-mesh-compile0.3.3 andaxiolid-contracts0.3.1. ClosedIfcPolygonalFaceSetbodies whose face corners lie on a straight run (collinear notch and window heads) now mesh closed and reportSolid(axiolid/kernel#170); before, the triangulation left T-junction cracks and they came backSurface. Surface models with a zero-area bowtie face, as Nova MEP exports write pipe-fitting end caps, now compile instead of being refused (axiolid/kernel#171). On 12 real models (66,659 products) this moves 1,999 products toSolidand failures from 2,291 to 755, together with the kernel#168 and #169 fixes already required. tests/meshing_coverage.rspins both: a real ArchiCAD lining at its exact coordinates meshes to its divergence volume as a solid, and a surface model with a bowtie cap keeps its area. A third test checks that an explicit chord budget (ExecutionOptions::with_chord_error, axiolid/kernel#165) brings the composite-curve D within 1e-5 of its exact volume.
ifc-georef
Added
ProjectedCrs.well_known_text: the OGC WKT literal of the one IFC4X3IfcWellKnownTextdefining the CRS, verbatim (#142).GeorefError::RuleViolation { entity, rule }for a schema WHERE rule or inverse cardinality a record breaks.
Changed (breaking)
ProjectedCrs.nameisOption<String>(#142). IFC4X3 declaresIfcCoordinateReferenceSystem.Name : OPTIONAL IfcLabelwithWHERE NameOrWKT : (HIINDEX(WellKnownText) = 1) OR EXISTS(Name): under an IFC4X3 header an unnamed CRS defined by exactly oneIfcWellKnownTextnow reads, one with neither is refused with the newGeorefError::RuleViolation { rule: "NameOrWKT" }, and two definitions for one CRS (the inverse isSET [0:1]) are refused too. IFC4, or a missing or ambiguous header, keeps requiring the name (MissingAttribute), sonameis alwaysSomethere. Migrate withcrs.name.as_deref().- The authoring drafts
ProjectedCrsDraft,GeographicCrsDraftandMapConversionDraftare#[non_exhaustive]: build them withProjectedCrsDraft::new(name),GeographicCrsDraft::new()orMapConversionDraft::new(source_crs, target_crs, eastings, northings, orthogonal_height)and a setter named after each optional field (.map_unit(unit),.x_axis((abscissa, ordinate)),.scale(s)). Fields stay public. - The read-side
ProjectToMapandProjectedCrsare#[non_exhaustive]; they can no longer be built with a struct literal outside the crate.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-material
Changed (breaking)
MaterialView::profile_set_usages()also yieldsIfcMaterialProfileSetUsageTapering, the schema subtype ofIfcMaterialProfileSetUsagein IFC4 and IFC4X3, in entity-id order, andMaterialProfileSetUsage::try_new/try_from_viewaccept it (#136). A caller that iterated bothprofile_set_usages()andtapering_profile_set_usages()sees each tapering usage twice; iterateprofile_set_usages()alone and branch on the newMaterialProfileSetUsage::tapering(), which returns the tapering projection (end_profile_set_id,cardinal_end_point) orNone.- The authoring drafts
MaterialDraft,LayerDraft,LayerSetDraft,MaterialAssignmentDraft,ConstituentDraftandProfileDraftare#[non_exhaustive]: build them withMaterialDraft::new(name),LayerDraft::new(thickness),LayerSetDraft::new(layers),MaterialAssignmentDraft::new(global_id, related_objects, relating_material),ConstituentDraft::new(material)orProfileDraft::new(profile)and a setter named after each optional field (LayerDraft::new(0.2).material(brick).priority(80)). Fields stay public. ResolvedAssignmentis#[non_exhaustive].
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). UnsupportedSchemareads "a release this crate has no verified layout for" instead of "no bundled schema table".- A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-properties
Added
exact_unitmapsIFCSECTIONALAREAINTEGRALMEASUREtoSECTIONAREAINTEGRALUNIT, whose name differs from the measure's. Before, it was refused as unmapped. The pairing follows the measure's definition (m^5) and the IFC4 annex E structural example.- The permissive views report the duplicates they resolve (#58).
exact_propertyalready refuses these files; the permissive views now keep a documented winner and say so:PropertyAnomaly::TypedTwice: an object with twoIfcRelDefinesByType(IFC4IsTypedByisSET [0:1]; IFC2X3IfcObjectWR1).PropertyAnomaly::DuplicateSetName: two same-named property sets on one occurrence or one type (IFC4UniquePropertySetNames). It is reported once per owner, including for a type that no occurrence uses.PropertyAnomaly::DuplicatePropertyName: two same-named properties in one set (IFC4UniquePropertyNames, IFC2X3WR32), reported byproperty_sets_by_object.
Changed (breaking)
PropertyAnomalyis#[non_exhaustive], so future checks can add variants. Exhaustive matches need a wildcard arm.template_of_setreturnsBTreeMap<EntityId, Vec<EntityId>>: every template defining a set, ascending by id and without repeats (#60).IfcPropertySetDefinition.IsDefinedByisSET [0:?] OF IfcRelDefinesByTemplate, so several templates are legal, but the oldBTreeMap<EntityId, EntityId>kept only the last and silently dropped the others, on valid files. A caller that wants one template must now choose, and is not handed an arbitrary one.
Fixed
- An object typed twice kept the last
IfcRelDefinesByTypein file order and said nothing. It now keeps the first by relationship id, and inherits only that type's sets. - Two same-named property sets on one owner made the later one win. For occurrences, the earlier one was misreported as
shadowed: shadowing means an occurrence set overriding a type set. The set with the lower id now wins within each route. An occurrence set still overrides a same-named type set, and that is not reported.
Fixed
exact_propertyno longer reportsAbsentfor a quantity in a same-namedIfcElementQuantity(#66). Quantity sets were skipped, so a checker asking forQto_WallBaseQuantities.Length(IDS treats quantities as properties) got a confident "missing".- Simple quantities now resolve exactly.
value_typeis the release's declared measure of the value attribute (IFCLENGTHMEASURE), andunit_idis the quantity'sIfcNamedUnit. - Complex, duplicated or malformed quantities are refused, as are a same-named property set and quantity set.
- A predefined property set (
IfcDoorLiningProperties, …) whose own attribute carries the requested name is refused withUnsupportedDefinitioninstead of being skipped intoAbsent. - A quantity set with no
Nameis refused (MalformedName), because it could be the set asked for. - Results change from
AbsenttoPresentor an error only where the old answer was unproven.
- Simple quantities now resolve exactly.
ifc-resource
Added
- IFC2X3 TC1 resource profile (#237).
ResourceView::for_modelandResourceView::newaccept anIFC2X3model and read it through IFC2X3's own bundled table, by attribute name. Shared concepts answer the shared accessors:ConstructionResource::identificationreadsResourceIdentifier,Person::identificationandOrganization::identificationreadId,Inventory::predefined_typereads the requiredInventoryType. IFC2X3-only accessors:ConstructionResource::resource_group,resource_consumption,base_quantity_measure,skill_set,suppliers,usage_ratio,sub_contractor,job_description, andInventory::last_update_calendar_date. NewMeasureWithUnitprojection (ResourceView::measure_with_unit) for the IFC2X3BaseQuantity. IFC2X3'sIfcPerson.WR1and the material/product resourceWR1/WR2are enforced. ResourceError::NotInSchema: the view's release does not declare the entity or attribute an accessor reads, in the form it reads it. IFC2X3 resource types,IfcResourceTime,LongDescription,Usage,BaseCosts,PredefinedType,Formula, the IFC4BaseQuantityandLastUpdateDateforms, and the IFC2X3-only accessors under IFC4/IFC4X3 refuse with it.
Changed
- Behaviour change: an
IFC2X3model is no longer refused byResourceViewwithUnsupportedSchema; it is read as above.ResourceEditor::for_modelstill refuses it withUnsupportedSchema(authoring stays IFC4/IFC4X3), and nothing is staged. - Reading an attribute the bound schema does not declare on the entity's type is now
NotInSchemainstead ofMissingAttribute;MissingAttributeis kept for a stored record shorter than its declared layout. No IFC4/IFC4X3 accessor reads an undeclared attribute, so this is reachable only through the IFC2X3 profile. - The crate status is
implemented; no gap remains open.
ifc-schedule
Added
read_work_calendarsandread_eventsbind the model's declared release liketasks(#212) and returnResult<_, ScheduleReadError>: IFC4X1, IFC4X2 and several schemas are refused withUnsupportedSchemaorMultipleSchemas; IFC2X3, which declares neither entity, has none (#234).WorkCalendarandEventexposerelease()andglobal_id(),Eventalsoobject_type()anduser_defined_trigger_type()(UserDefinedEventTriggerType);WorkTimeandEventTimecarrydata_originanduser_defined_data_origin, andEventTimeitsname(#234).TaskTime::is_recurring,TaskTime::recurrence_refandTaskTime::recurrenceexpose anIfcTaskTimeRecurring's pattern (#235).Task::schedule_time_controlsand theScheduleTimeControlview read the IFC2X3IfcScheduleTimeControlthatIfcRelAssignsTasks.TimeForTaskassigns to a task, every attribute by name from IFC2X3 TC1: dates asAuthoredDateTimerecords, durations and floats asIfcTimeMeasurenumbers. ATimeForTaskof another type is reported as the newTaskTimeAnomaly::NotAScheduleTimeControl(#235).RecurrenceDraft::time_periodsplacesIfcTimePeriodrecords in a pattern'sTimePeriods(#233).create_recurrence_pattern_inandcreate_time_period_inbind the model's declared release: IFC2X3, which declares neither entity, is refused withEntityNotInSchema, and the pattern writer refuses a period reference that is absent (MissingReference) or not anIfcTimePeriod(WrongReferenceType). Nothing is staged on a refusal.Recurrenceexposesdays(DayComponent),months(MonthComponent) andtime_periods, read as the newTimePeriod(StartTime,EndTimeas authored);recurrence_pattern(model, id)reads a pattern referenced from outside a work calendar, such asIfcTaskTimeRecurring.Recurrence, by name in the declared release, returningResult<Option<Recurrence>, ScheduleReadError>(#234);recurrence_slot::TIME_PERIODSandtime_period_slotname the slots (#233).Lagexposesduration_type(IfcLagTime.DurationType) andname(#236).process_execution_order: everyIfcProcess(task, procedure, event) in a deterministic execution order (#236).ScheduleReadError::SequenceDepthExceeded { start, limit }(#236).
Deprecated
work_calendarsandevents: useread_work_calendarsandread_events. They keep their signatures and now read by name in the declared release too, but read a header the readers cannot bind as IFC4, as before, instead of refusing it (#234).
Fixed
The calendar, event and recurrence readers find every attribute by name in the declared release instead of reading fixed IFC4 positions; IFC4X3's
IfcWorkTime.StartDateandFinishDatewere read only because they sit where IFC4'sStartandFinishdo (#234).Task::timeaccepts anIfcTaskTimeRecurring(IFC4, IFC4X3) through the release's subtype table; it was reported asNotATaskTimeand dropped (#235).create_task_time_recurringbinds the model's declared release: IFC2X3, which declares noIfcTaskTimeRecurring, is refused withEntityNotInSchema, nothing staged (#235).tasks_of_schedulereads everyIfcRelAssignsToControlsubtype in the bound release, so an IFC2X3 task assigned withIfcRelAssignsTasksis listed (#235).create_recurrence_patternwrites all eight attributes IFC4 and IFC4X3 declare forIfcRecurrencePattern; it wrote seven, so every authored pattern was a short record (#233).A sequence walk that reaches
MAX_SEQUENCE_DEPTHis refused withSequenceDepthExceededinstead of returning a truncated result as if complete:downstream_ofandfind_cyclereport it (#236). The walk uses an explicit stack, so the budget rather than the thread's stack bounds it.find_cyclewalks from everyIfcProcessin the declared release, not onlyIfcTask, so a cycle through events or procedures is found (#236).execution_ordersorts over everyIfcProcessand keeps the tasks, so a constraint through an event or procedure (task A, event E, task B) orders A before B, and a cycle through one is refused (#236). Its result is still tasks only.
ifc-spatial
Changed (breaking)
serve_buildingstakes the&Model, binds its declared release and checks its targets (#286), asifc_systems::serve_buildingsdoes (#277). It accepted any targets: per the EXPRESS sourcesRelatingSystemmust be anIfcSystemandRelatedBuildingsaSET [1:?] OF IfcSpatialStructureElement(IFC2X3 TC1) orOF IfcSpatialElement(IFC4 ADD2 TC1, IFC4X3 ADD2). Without the model it could not see either end. Like the other release-bound plain writers it refuses IFC2X3, which requiresOwnerHistory, withAuthoringRequired; useserve_buildings_with_owner_historythere. IFC4 and IFC4X3 records are unchanged, slot for slot.- Behaviour change:
serve_buildingsandserve_buildings_with_owner_historynow refuse, staging nothing, input they used to write: a system that is missing (MissingReference) or not anIfcSystem(WrongReferenceType), a building that is missing or not admitted by the declared release'sRelatedBuildings(WrongReferenceType; anIfcSpatialZoneis admitted in IFC4 and IFC4X3, not in IFC2X3), a repeated building (Invalid, the attribute is aSET), and a second relationship for a system that already services buildings in the model or on the transaction (Invalid;IfcSystem.ServicesBuildingsisSET [0:1]). The writers moved toauthoring::services; the public paths are unchanged.
ifc-structural
Changed (breaking)
LoadKindis#[non_exhaustive]and gainsSingleForceWarping,SingleDisplacementandSingleDisplacementDistortion(#228). An exhaustivematchon it no longer compiles; add a wildcard arm.
Added
- IFC2X3 varying linear and planar actions (#229).
StructuralAction::is_varying,varying_applied_load_locationandsubsequent_applied_loadsreadIfcStructuralLinearActionVaryingandIfcStructuralPlanarActionVarying; the loads come back in list order asStaticLoad, theLIST [1:?]/LIST [2:?]minimums are enforced withInvalidCardinality, and all three returnNone/falseunder IFC4 and IFC4X3, which declare neither entity.ActionDraft::varyingwith the newVaryingActionDraftstages either subtype from aLinearorPlanarkind; outside IFC2X3 it refuses with the newStructuralError::EntityNotInSchema, and a short list, another kind or a wrong reference refuses before anything is staged. StructuralView::surface_reinforcement_areaand theSurfaceReinforcementAreaprojection forIfcSurfaceReinforcementArea(IFC4, IFC4X3), whichstage_loadalready authored (#228). It enforcesSurfaceAndOrShearAreaSpecified,NonnegativeArea1..3and theLIST [2:3]bounds; an IFC2X3 view refuses it withUnsupportedSchema.
Fixed
StructuralView::load/static_loadreadIfcStructuralLoadSingleDisplacement,IfcStructuralLoadSingleDisplacementDistortionandIfcStructuralLoadSingleForceWarpinginstead of refusing them withWrongType(#228). Classification usesSchema::is_a, most specific subtype first, andcomponents()returns the displacement and rotation slots, withDistortionorWarpingMomentappended for the subtypes.
ifc-style
Changed (breaking)
AppearanceSupportis#[non_exhaustive]: a match needs a wildcard arm;AppearanceDeclarationis#[non_exhaustive].- Every authoring draft is
#[non_exhaustive], so a struct literal no longer compiles outside the crate. Each gainsnew(required…)and one builder setter per other field, named after the field and taking the unwrapped value:BlobTextureDraft::new(raster_format, raster_code),TextModelDraft::new(),LightSourceDraft::new(light_colour),AnnotationDraft::new(global_id),TextLiteralDraft::new(literal, placement, path),TextLiteralWithExtentDraft::new(literal, placement, path, extent, box_alignment),AnnotationFillAreaDraft::new(outer_boundary),CurveStyleDraft::new(),PixelTextureDraft::new(width, height, colour_components, pixel),ColourRgbDraft::new(red, green, blue),SurfaceStyleShadingDraft::new(surface_colour),SurfaceStyleDraft::new(side, elements),StyledItemDraft::new(styles),PresentationLayerDraft::new(name, assigned_items),SurfaceStyleRenderingDraft::new(surface_colour, reflectance_method)andImageTextureDraft::new(url_reference). Fields stay public.
Added
StyleError::UnsupportedSchema:StyledItem::stylesrefuses a recognised release it is not verified for instead of applying the IFC4X3 rule to it.
Changed
- Links no bundled schema table itself: every entry point takes the
Schemafrom the caller. A consumer that used a table through this crate's dependency (ifc_schema::ifc4()) enables it on its ownifc-schemadependency (default features bundle every release). StyledItem::stylesreads IFC4X1 and IFC4X2 like IFC4: both declareStylesoverIfcStyleAssignmentSelect, which still admitsIfcPresentationStyleAssignment; only IFC4X3 narrowed it. Pinned against both bundled tables.
ifc-systems
Changed (breaking)
- Release features
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all default, forward toifc-schema, which this crate now depends on without its default features (#306). Each links one release's table, so a single-release build carries only that table: an IFC4-only browser module with systems shrinks from 1,423,611 to 831,006 bytes. The default build links every release, as before. - Authoring refuses a release the build leaves out with
SystemAuthoringError::UnsupportedSchemainstead of panicking; reads already refused it. A build without default features therefore has to name the releases it reads.
Under 0.x, a minor release.
ifc-template-catalog
Changed (#317)
- The embedded snapshots are one container,
data/catalog.bin(snapshot format 3), instead of three bincode files: 1,423,604 bytes instead of 3,648,573 (547,657 instead of 966,507 undergzip -9). Strings used more than once are stored once in a table, strings used once stay inline, and each distinct property, quantity and set record is stored once across editions; each edition keeps its manifest, source digest and per-template provenance. Lossless: every edition decodes, in the official and the corrected profile, to exactly the catalogs format 2 did, which a test asserted against the old files before they were removed; content fingerprints taken from them stay pinned intests/snapshot.rs. - The first lookup of an edition decodes that edition only.
- The generator (
ifc-template-catalog-generate) writes one edition into the container and keeps the others; the hiddengenerationmodule and the format-2 codec are gone.
Added (#317, #318)
- Module
snapshot:encode(a container or a per-edition file, deterministic),decode_edition,decode_all,editions,file_name, the pinsCONTAINER_SHA256andpinned_sha256,EDITIONS,FORMAT_VERSIONandEncodeError.ArchiveErroris public there and gainsMissingEdition. - Feature
runtime, moduleruntime:installan edition from its per-edition file or the container, checked against the pinned SHA-256 (RuntimeCatalogError::DigestMismatchotherwise), thenload_catalogin the official or corrected profile,is_installedanddecode_verified; an edition not installed isRuntimeCatalogError::NotInstalled. Builds for wasm32;embeddedstays the default. - Example
export_snapshotswrites the per-edition files and checks each pin; the npm package ships them.
Semver
- A new public module, feature and error variant, and a changed data layout under
data/: a minor release (0.4.0). The API callers used (embedded::*,Catalog, the definitions) is unchanged.
ifc-validate
Added
- Aggregate checks from the schema's bounds (#111): a level outside its declared size (
structure.aggregate.too_few,structure.aggregate.too_many;ARRAY [l:u]needs exactly u-l+1), an inner level of a nested aggregate that is not an aggregate (structure.aggregate.nesting), and a repeated element in aSETorUNIQUElevel (structure.aggregate.duplicate). The members of aLIST OF LISTare now type-checked against the innermost element type (#215). - Every
UNIQUEclause of the declared release is checked across the declaring entity and its subtypes (structure.unique.violation), exceptIfcRoot.UR1, which staysglobal.UniqueGlobalId.
Changed (breaking)
structure::duplicate_global_idsand its rule idstructure.unique.duplicate_global_idare removed: the function duplicatedglobal.UniqueGlobalIdandvalidatenever ran it. Its module now checks the release's UNIQUE clauses (structure::unique_rules, run byvalidate).- No registered rule claims to need aggregate bounds any more:
IfcPolyLoop.WR21andIfcPolyLoop.AllPointsSameDimare unsupported for needing an expression evaluator. Supportis#[non_exhaustive]: a match needs a wildcard arm.Findingis#[non_exhaustive]; it can no longer be built with a struct literal outside the crate.Pathis#[non_exhaustive], so a later release can name a new location kind; a match needs a wildcard arm.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). validate_declaredvalidates IFC4X1 and IFC4X2 files against their own bundled tables instead of refusing them as unknown. No WHERE rule is registered for either release yet, so their report carries onewhere.releasefinding (severityUnsupported) saying WHERE rules were not evaluated, rather than reading as if they passed.- SELECT resolution treats a type declaration form
ifc-schemaadds later like an undeclared member (fails closed); followsifc_schema::TypeKindbecoming#[non_exhaustive].
ifc-xml
Changed (breaking)
- A codec built with
XmlCodec::with_schemaorXmlCodec::with_schema_and_profilenow reads strictly (SchemaReading::Strict, #266). Every value is typed from its attribute's declaration instead of inferred from its text:Name="1"reads as the label'1'(it read as the integer1), an enumeration's text is checked against its members, akindthe declaration does not admit (an integer in a label, a bare value in a SELECT, a typed value outside it) is refused. An XML attribute or child element the entity does not declare is refused withXmlError::UnknownAttribute, naming entity, element and attribute; it was placed after the declared slots. An entity the schema does not declare, an abstract one, and a reference to a missing entity or one of a type the declaration does not admit are refused too. The old read silently misread such content, so the safe read is now the default;.with_reading(SchemaReading::Lenient)restores it, and the codec without a schema is unchanged. Unknown entities and attributes still round-trip through the schema-less codec and the lenient read. - The schema-aware writer writes a value as an XML attribute only where the strict read types its text back to the same value; elsewhere it uses the explicit
kindelement, which the lenient read accepts and the strict read refuses unless the declaration admits it. Output for models the schema describes is unchanged.
Added
XmlCodec::xsdandXmlLayout::Xsd: a reader of the buildingSMART ifcXML configuration the release XSD declares (#265), for IFC4 ADD2 TC1 and IFC4X3 ADD2. Entities nested and defined in place,ref/hrefreferences withxsi:nil,xsi:type, inverse attributes (which supply the attribute they invert), space-separated and flattened list attributes,-wrapperandSeq-...-wrappervalues, and enumeration, boolean, logical and binary text typed from the schema, read into the same model as the STEP form: entity type, typed-parameter and enumeration names upper-case, entities numbered in document order. Refused with a typed error rather than approximated: a name the entity does not declare, a value its declaration does not admit (including decimal commas), a flattened nested aggregate whose inner sizes neither the schema nor anarraySizefixes, an inverse contradicting its relationship or implying a position in an ordered list,pos/pathaddressing, externalhrefs, partial-byte binaries. A value for a slot a subtype redeclares DERIVE, which the XSD still admits as an XML attribute, is typed and read as derived. The layout is read only; writing it is refused.XmlProfile::Ifc4x3Add2,XmlProfile::namespaces(IFC4 ADD2 TC1 also accepts thehttp://www.buildingsmart-tech.org/ifcXML/IFC4/Add2namespace its own Annex E examples declare, in the XSD layout only) andXmlProfile::version.SchemaReading,XmlCodec::with_reading,XmlCodec::readingandXmlCodec::layout.XmlErrorvariantsUnknownEntity,AbstractEntity,UnknownAttribute,TypeMismatch,WrongForm,UnresolvedReference,DuplicateId,InverseConflict,SchemaMismatchandUnsupported.XmlCodecimplementsDebugandClone;XmlErrorimplementsClone.- A schema-backed test checks the XSD reader's configuration against every entity type of both release XSDs, which
scripts/fetch-ifc-schemas.shnow fetches (checksummed, never committed). An env-gated test compares paired STEP and ifcXML files (tests/xsd_corpus.rsdocuments how to run it). tests/xsd_output.rs, an opt-in (--ignored) check that writes every IFC4 and IFC4X3 fixture with the strict profile and validates it withxmllintagainst the fetched release XSD (#117). It fails on malformed output, a namespace that is not the XSD'stargetNamespace, a control document the XSD orXmlCodec::xsdrefuses, and any XSD error beyond the documented native-layout departures. The publishedIFC4X3_ADD2.xsddoes not compile in libxml2 or Xerces, which the check records. How to run it is in the README.
Security
- Require
quick-xml0.42 (was 0.37), which fixes RUSTSEC-2026-0194 (quadratic duplicate-attribute check on one start tag) and RUSTSEC-2026-0195 (unbounded namespace-declaration allocation inNsReader); both are denial of service on untrusted input (#267).
Changed
impl From<quick_xml::Error> for XmlErroris kept, but its source type is now quick-xml 0.42'sError. Code that converts a quick-xml 0.37 error intoXmlErrormust upgrade quick-xml too. Values read are unchanged: entity and character references are still resolved, and literal tabs and line breaks in attribute values and text are still kept rather than normalised to spaces; a regression test pins this.
Documentation
- Strict-profile output is documented as what it is: the crate's own layout under the release XSD's target namespace and schema token, not the XSD configuration, and not valid against the release XSD (#117).
XmlProfileno longer calls the XSD "bundled"; it is fetched, never shipped.
openbim-ifc
Added
- Textured tessellated geometry:
ifc::geometry(ifc-geometry 0.3) lowersIfcIndexedTriangleTextureMapas a per-corneruvchannel that survives compilation, including multi-item bodies and mirroredIfcMappedItems (#30, axiolid/kernel#115).
Changed
- Breaking: re-exports ifc-geometry 0.3, ifc-georef 0.3 and ifc-alignment 0.3, which all require Axiolid 0.3.
openbim-ifc-cli
Added (#469)
convert-units INPUT OUTPUT --unit UNIT [--keep-area-volume] [--keep-replaced-units] [--dry-run] [--format human|json] [--force]: the facade'sconvert_length_uniton a file, written as STEP. A value the conversion cannot carry refuses the run (exit 2) with each one named, and nothing is written.
Semver: minor (a new command).
Added (#472, #474)
edit FILE QUERY --set PATH VALUE... (-o FILE | --in-place | --dry-run)writes values by path on the entities a selector selects, as one checked transaction, each value read as its target's type;--dry-runlists the changes and writes nothing; the input is never changed without--in-place, which keeps its format;--nullnames the text of$;--format jsonreports changes and refusals as one document.export-table FILE QUERY [-c PATH]... [-o FILE] [--to csv|tsv|xlsx] [--key global-id|id] [--null TEXT]writes the selected entities as a table, one column per value path.import-table FILE TABLE (-o FILE | --in-place | --dry-run) [--from csv|tsv|xlsx] [--key ...] [--null TEXT]applies a table back by key as one checked transaction; an unchanged table writes an identical model.- Exit code 3:
editorimport-tablerefused a planned change; every refusal is printed with its entity and path or its row and column and a kind (invalid-text,type-mismatch,not-settable,conflicting-write,property-edit,invalid-headerwith its span,unknown-key,ragged-row, ...), and nothing is written. relations FILE ENTITY [--traverse-up]lists what one entity is related to by kind (container, parent, type or occurrences, material and materials, classification, groups, openings, fillings, voided element) and every relationship naming it;--traverse-upthe chain of parents.materials FILE [QUERY]shows each selected entity's material definition with its layers, profiles, constituents or list members.cost FILE [--depth N]shows every cost schedule as a tree of cost items with their values, direct total and rolled-up total;schedule FILE [--depth N]every work schedule and work plan as a tree of tasks with their scheduled times. All four write--format humanorjson.- The command now checks
Pset_andQto_writes against the embedded PSD/QTO catalog (facade featureproperty-catalog), which grows the binary.
Semver: minor (additive; exit codes 0, 1 and 2 keep their meaning).
[0.3.3] - 2026-10-20
ifc-schema
Added (#467)
Semver: patch (0.x: a new public type only, nothing changed). SchemaVersion and its header-token mapping are unchanged.
UnpublishedRelease(Ifc4x3Tc1,Ifc4x3Add1) names the IFC 4.3 releases whose identifiers files carry but whose normative EXPRESS buildingSMART does not publish:IFC4X3_TC1(IFC 4.3.0.1) andIFC4X3_ADD1(IFC 4.3.1.0), each withidentifier(),version_number(),superseded_by()(IFC4X3 ADD2, not a claim of identity) andfrom_header_token(). The identifiers and numbers come from theSCHEMA_NAMEand documentation header of buildingSMART/IFC4.x-development's generator; buildingSMART lists only ADD2 (4.3.2.0) for IFC 4.3, so no tables can be fetched and checksummed for TC1 or ADD1, and IfcOpenShell's own (non-normative) tables show TC1 lacks entities ADD2 has.SchemaVersion::from_header_tokenstill returnsNonefor both: they are named for a precise refusal, never read as ADD2 by assumption.
[0.3.2] - 2026-10-14
ifc-alignment
Fixed
Referent authoring writes the SELECT values typed (#201):
point_by_distancewritesDistanceAlongasIFCLENGTHMEASURE(..). It is anIfcCurveMeasureSelect, where the wrapper is what tells a length from a curve parameter. The offsets stay bare.stationingwritesPset_Stationing'sNominalValues, declaredIfcValue, asIFCLENGTHMEASURE(..)forStationandIncomingStationandIFCBOOLEAN(..)forHasIncreasingStation.
station_equationsreads both forms, as before.
ifc-author
Fixed (#481)
EntityBuilderchecks a nested aggregate (LIST [1:?] OF LIST [3:3] OF IfcLengthMeasure, asIfcCartesianPointList3D.CoordListandIfcTriangulatedFaceSet.CoordIndexdeclare) at its elements, level by level, instead of refusing the inner lists as values of the element type; a level that should hold aggregates and holds a scalar isAggregateMismatch.
Semver: patch.
ifc-schema
Changed
Schema::is_a,supertypesandattributesread a supertype chain and a positional attribute layout precomputed per entity when the schema is assembled, instead of walking the declarations on every call (#352).is_ano longer allocates, and no case-folded lookup (entity,type_def,direct_subtypes) allocates for a name of up to 128 bytes. The answers are unchanged: the tables are built by the walks they replace, andtests/precomputed_lineage.rscompares every entity of every bundled release, under three spellings, against a frozen copy of the old walks, andis_afor every ordered pair.accepts_typeanswers two distinct declared entities withis_adirectly, without the select walk's allocations; the answer is the one the walk gave.
Added
Schema::attribute_count(name)andSchema::attribute_at(name, slot):attributes(name).len()andattributes(name).get(slot)without allocating (#352).
Semver: additive, same answers: a patch release.
[0.3.1] - 2026-10-04
ifc-alignment
Changed
profile_law(and solower_gradient_curve) now refuses a vertical profile whose seams do not join: a segment'sStartHeightmust match the previous segment's end height and itsStartGradientthe previousEndGradient, within the same magnitude-scaled tolerance already used forStartDistAlongcontiguity. A height step or grade kink was previously accepted and silently shifted every downstream height. The refusal is the newAlignmentError::ProfileDiscontinuity, naming both segments, the discontinuous quantity (ProfileSeam::HeightorProfileSeam::Gradient) and both values (#95).
ifc-author
Fixed (#330)
- A built-in type with a width (
STRING(255),STRING(22) FIXED,BINARY(32)) is the shape of its keyword. IFC4 and IFC4X3 declareIfcLabel = STRING(255), so a number set as anIfcLabelwas accepted as unresolvable; it is now refused withTypeMismatch, as a number where a bareSTRINGis declared always was. A bug fix: a patch release.
ifc-control
Added
- Borrowed read views for the four controls (#100):
read_controlandread_controlsreturn aControlwithGlobalId,OwnerHistory,Name,Description,ObjectType,Identification(IFC2X3PermitID,RequestID,ID),PredefinedType,Status,LongDescriptionandLifeCyclePhase, each found by name in the model's declared release, plusControl::declaresandControl::assignments(ControlAssignmentviews of theIfcRelAssignsToControlthe crate writes). A record that does not fit its release is refused with the newControlError::InvalidAttributeorControlError::ExtraAttributes, or withMissingAttribute; a foreign type withForeignControl. ControlKind::from_type_name.
ifc-geometry
Added
compile::compile_product_mesh_reported(and_with) return aCompiledMesh: the triangles plus the kernel'sMeshClosure, i.e. whether they bound a solid.CompiledMesh::solid_mesh(product)returns the mesh only forSolidand otherwise refuses with the newGeometryError::NotASolid, naming the product. AnIfcShellBasedSurfaceModelnow compiles (axiolid/kernel#161) but is aSurface: without this a caller summing the divergence of its triangles gets a volume the file never claimed, finite and plausible for a closed shell. A backend that does not report closure givesUnknown, which is refused as well.NetMesh::closurecarries the flag for net bodies.compile_product_meshis unchanged and still returns the bare mesh.tests/meshing_coverage.rsand the generated public fixturetest/fixtures/synthetic-coverage/meshing_coverage.ifc(#47): one product for each product-meshing failure kind #47 measured on real models, compiled throughcompile_product_mesh. The four kinds fixed here (#43 to #46) pin exact volumes, cross-checked with IfcOpenShell 0.8.5. The two fixed in the Axiolid reference compiler (axiolid-mesh-compile0.3.1) pin their answer too: polygonal faces with more than 3 corners (axiolid/kernel#160) mesh the 2 m quad cube to 8 m3, and the open-shell surface model (axiolid/kernel#161) meshes its 0.12 m2 and refuses any volume.DegenerateFacePolicy(#46), set per session withLoweringSession::with_face_policy. The default,Refuse, is unchanged: anIfcPolyLoopwith fewer than three distinct edges refuses the brep, naming the loop.DropAndReportleaves out a face whose outer (or only) bound collapses, since it covers no area, and lists it inProvenanceMap::dropped_faces. A collapsed hole in a face with real area is still refused, and a shell whose every face collapses is refused asDegenerate. The policy drops exactly what the default refuses.- On OfficeBuilding.ifc all 16
IfcWindows refused this way (two shared loops of the form(A, A, B, B)) compile underDropAndReport, each reporting its one dropped face; their volume, 0.1707752 m3, matches IfcOpenShell 0.8.5. No other product in eight real models changes. IfcArbitraryClosedProfileDefandIfcArbitraryProfileDefWithVoidsnow lower anIfcCompositeCurveouter or inner boundary (#43). Segments may beIfcPolyline,IfcTrimmedCurveoverIfcCircleorIfcLine, or a nestedIfcCompositeCurve. Arcs stay exactCircle2segments.SameSense,SenseAgreementandMasterRepresentationare honoured, and a trim may be a parameter (in the project's plane-angle unit) or a cartesian point.- On the two real models that carried them, all 128 products refused for a composite profile boundary now compile, and no other product changed.
- Net geometry (#44, ADR 0014):
compile::compile_product_mesh_net(and_withfor your own backend) returns a product's Body with everyIfcRelVoidsElementopening subtracted, plus the ids of the openings removed.compile_product_meshis unchanged and stays gross: quantity takeoff wants gross, clearance and ratio checks want net. The graph-level entry point islower::lower_product_net; the kernel-free relation reader isopenings_of. - An opening that cannot be removed -- its Body does not lower, it has none, it is not a solid, or the backend refuses it or its cut -- is
GeometryError::OpeningNotSubtractednaming the host and the opening. The gross body is never returned in its place. A host whose own Body the backend refuses staysCompilationRefusedon the host. - Multi-item hosts and openings (a Body with several items, or a mapped item) are cut item by item: every host part minus every opening part. A mapped opening is flattened through its instance transforms, composed outer after inner.
Changed
- The workspace requires
axiolid-mesh-compile0.3.2 andaxiolid-mesh-compile-contract0.3.1. With 0.3.0 anIfcPolygonalFaceSetwith any face of more than 3 corners, or with voids (IfcIndexedPolygonalFaceWithVoids), was refused asUnsupported(Tessellation)(axiolid/kernel#160), and everyIfcShellBasedSurfaceModelas "brep has no solid" (axiolid/kernel#161). 0.3.1 triangulates such faces in their own plane and refuses a non-planar one by face index. With 0.3.1 a bend trimmed from a circle across its seam, as Revit writes a bar's bends (270 -> 45or270 -> 15degrees), was sampled around the wrong side of the circle, so the bend missed the next leg and the bar was refused ascomposite directrix has a N unit gap(axiolid/kernel#168). 0.3.2 runs the trim from its first end the way its sense says, wrapping past the seam. On the Revit rebar model below that compiles the last 1,494 refused bars: 40,990 of 41,019 products compile, and no local model refuses a directrix gap. - The workspace requires
axiolid-construct0.3.2 (reached throughaxiolid-mesh-compile, pinned only as a floor behindcompile-reference-backend). With 0.3.0 two compiled results were wrong or refused, though lowering was right: anIfcPolygonalBoundedHalfSpacewhosePositionis translated within the clip plane cut the wrong region with no error (axiolid/kernel#164), and an opening body extruded downward, as Solibri and Revit hang windows from the lintel, was wound inside-out, so its subtraction was refused (axiolid/kernel#166; 78 of 423 real hosts refused with 0.3.0, 14 with 0.3.1). Both tests that pinned these now run. With 0.3.1 a swept disk was oriented by one fixed axis seeded from its first segment: a bent bar whose later leg ran along that axis was refused, and a leg nearly along it twisted the tube so its volume came out low with no error (axiolid/kernel#169). 0.3.2 carries the frame along the path. On the 41,019-product Revit rebar model below, the 878 refused bars compile and swept-disk bars within 0.5 % of their closed-form volume go from 30,199 to 34,003 of 39,215; none is more than 1 % off. - A gap between consecutive composite segments, or between the last and the first, wider than 1e-5 m is refused as
Degenerate, naming the segment and the gap. It is never bridged with an edge the file did not author. - Any other segment parent, a reparametrised segment, and a conic placed with a 3D placement stay typed
Unsupported, naming the entity.
Known limits
- The compiled mesh of a curved profile is only as close to the exact area as the kernel's chord budget allows. Up to
axiolid-mesh-compile0.3.2 that budget equals the linear tolerance, which is coarse for small radii: a real gutter profile meshes 1.9 % over its exact area and a slot 0.7 % under (axiolid/kernel#165). Lowering is exact; the arcs reach the kernel as arcs. - On the local real-model corpus (423 hosts with openings whose gross Body compiles, six models) 14 hosts are still refused: the kernel boolean refuses a non-manifold operand, and each refusal names the opening. No host that already netted changed volume between kernels.
- Measured against IfcOpenShell 0.8.5 on 138 sampled hosts across four real models: 130 agree within 0.1 % (median difference about 1e-10). The other 8 are not subtraction errors. On 7, IfcOpenShell closes a gap in the host's composite-curve profile with a segment the file never authored; our gross matches an independent exact integration of the profile to 1e-4 on the 4 checked, IfcOpenShell's is off by up to 7 %. On 1, IfcOpenShell's own boolean fails and it returns the host uncut.
Fixed
IfcPolygonalBoundedHalfSpaceclips now compile (#45).PolygonalBoundarywas lowered through the 3D curve path as aCurve3, but Axiolid'sBoundedHalfSpacecontract and reference compiler require aCurve2, so every suchIfcBooleanClippingResultwas refused withhalf-space boundary .. is not a Curve2 nodealthough lowering succeeded. The boundary now lowers as aCurve2polyline inPosition's XY plane, lengths converted to metres. A 3D boundary point is accepted only withz = 0; any otherzviolatesBoundaryDimand is refused asDegenerate, naming the point, instead of being projected.IfcSweptDiskSolid,IfcSweptDiskSolidPolygonal,IfcFixedReferenceSweptAreaSolidandIfcSurfaceCurveSweptAreaSolidreadStartParam/EndParamin their directrix's own parameterisation. On anIfcCompositeCurvethat is not a length: ISO 10303-42 accumulates each segment's parametric length, 1 perIfcPolylineedge and a trimmed segment's own trim span, so an arc contributes its ANGLE in the file's plane-angle unit (IFC4IfcCompositeCurve, figure 389: a line plus a 90 degree arc is 91). It was converted as a length and handed to the kernel, which measures arc length, so a Revit rebar authored(0, 365)over five 1-unit legs and four 90 degree bends was read as 365 m and refused, and a range that happened to fit silently cut the bar short. A full range now keeps the authored directrix; a partial range is cut exactly at the composite's parameters before the kernel sees it, honouringSameSense,SenseAgreement,ParamLengthand arcs across the circle's seam. A range past the composite's parametric length isDegenerate, naming the sweep and the length; a range over a segment trimmed only by points isUnsupported, since its parametric length is not stated. A trimmed-curve directrix now converts by its basis, like any trim, instead of always as an angle.- On the local real-model corpus (eight models) this changes one model: in a 41,019-product Revit rebar model, compiled products go from 19,832 to 38,618. Checked against the closed-form volume (inscribed disk polygon times exact path length) of all 39,215 swept-disk rebars: before, 25 were within 0.5 % and 18,077 compiled wrong, up to 96 % short; with this fix alone (
axiolid-construct0.3.1) 30,199 are within 0.5 %, and 34,003 with the kernel floors above. No product in any other model changed volume.
ifc-model
Fixed
Model::type_histogramno longer lists a type with a count of zero after its last entity is removed (remove), retyped (retype) or replaced under the same id by another type (insert). The emptied type-index bucket is now dropped, so the histogram equals that of a model rebuilt from the same entities (#106).ids_of_typewas already correct (an empty slice either way).
Added
tests/index_coherence_property.rs: seeded random sequences ofinsert,push,remove,retype,set_attribute(s)and committed, refused and stale transactions, checked after every step against a naive shadow store, a scan, a model rebuilt from scratch and an independent reference walk forReverseIndex; a refused transaction must leave the model unchanged, revision included (#106). No new dependency: a local SplitMix64 over fixed seeds keeps it deterministic.
ifc-schema
Changed
- The
expressandgenerationfeatures use the workspace'sopenbim-steppin,=0.11.0(was its own=0.10.0), shared withifc-step, so a build withexpresslinks oneopenbim-step(#288). 0.11 changed no EXPRESS extraction: the bundled tables still match the fetched schemas. Noopenbim-steptype is part of this crate's API, and the default build links none.
ifc-systems
Added
What a system serves (#230).
Systemgainsserviced_buildings, read fromIfcRelServicesBuildingsin IFC2X3, IFC4 and IFC4X3, andserviced_facilities, the IFC4X3IfcSystem.ServicesFacilitiesview (the structures whoseIfcRelReferencedInSpatialStructurelists the system; empty under IFC2X3 and IFC4, whoseRelatedElementscannot hold a system). The admissible target type comes from the declared release's table (IfcSpatialStructureElementin IFC2X3,IfcSpatialElementafter). NewSystemAnomalyvariants report a second relationship for one system (ServicesBuildingsTwice;ServicesBuildingsisSET [0:1]) and a target the release does not admit (ServicedNotSpatial); dangling targets areDangling, a non-system relating endNotASystem.serve_buildingsandserve_buildings_with_owner_historystage anIfcRelServicesBuildingsin the model's declared release, refusing a relating end that is not anIfcSystem, a target the release'sRelatedBuildingsdoes not admit, a missing reference, a repeated target, and a system that already services buildings in the model or on the transaction. A refusal stages nothing.Systemgainslong_nameandpredefined_type(#231): theIfcDistributionSystemLongNameandPredefinedType(IfcDistributionSystemEnumtoken), read by attribute name in the declared release's table, forIfcDistributionSystemand its subtypeIfcDistributionCircuitunder IFC4 and IFC4X3. Both areNonefor every other system type and on IFC2X3, which has noIfcDistributionSystem.
ifc-template-catalog
Security
- With the
xmlfeature, requirequick-xml0.42 (was 0.37), which fixes RUSTSEC-2026-0194 (quadratic duplicate-attribute check on one start tag) and RUSTSEC-2026-0195 (unbounded namespace-declaration allocation inNsReader); both are denial of service on untrusted input (#267). PSD and QTO import reads the same values as before: references are resolved, and literal tabs and line breaks in attributes and text are kept.
ifc-validate
Added
The form of every value is checked against ISO 10303-21:2016 (#199): a typed parameter is written exactly where the declared type is a SELECT (§12.1.8), and the bare value everywhere else (§12.1.6, §12.1.7). Three new rule ids, all errors, each pinned by adversarial fixture pairs:
type.typed.outside_select: a typed parameter of the declared type, or of a specialisation of it, where the declared type is not a SELECT.IFCAREAMEASURE(12.5)inIfcQuantityArea.AreaValueis reported; it used to pass because the payload was judged against the wrapper alone.type.typed.wrong_type: a typed parameter of another type there.IFCLABEL('x')inAreaValueused to pass as well.type.select.untyped: a bare value that is not a reference where the declared type is a SELECT, such asNominalValue : IfcValuewritten1.instead ofIFCREAL(1.). Part 21 requires the typed form for every SELECT, not only ambiguous ones. A SELECT of entities alone still reportstype.entity.expected_reference.
All three apply to aggregate members against the element type, and to the parameter inside a typed wrapper against the wrapper's type. Whether the declared type is a SELECT follows defined-type aliases, as §12.1.8 EXAMPLE 2 encodes a type aliasing a SELECT. They are errors, not warnings, although many readers unwrap a well-typed wrapper: the file is not legal, and
ifcopenshell.validaterejects both forms. The reasoning is in thetype_checkmodule docs.type_check::Mismatchhas the variantsTypedOutsideSelect,TypedWrongTypeandUntypedSelectValue.
Changed
type.select.memberwalks only nested SELECTs, never the underlying type of a defined type in the select-list: §12.1.8 requires the keyword to name a type the SELECT, or a SELECT nested in it, lists.IFCRATIOMEASURE(0.5)in anIfcColourOrFactorslot, which listsIfcNormalisedRatioMeasure, is now reported.
openbim-ifc
Added
ifc::schemare-exportsifc-schema(featureschema), so the bundled schemasids_of_type_including_subtypesneeds, such asifc::schema::ifc4(), are reachable without a directifc-schemadependency. Found by building a crates.io-only consumer of 0.3.0.
[0.3.0] - 2026-10-14
ifc-alignment
Changed
- Breaking: requires Axiolid 0.3. Lowered alignment curves are returned as
axiolid_model::GeometryGraphandNodeId, so the major Axiolid version is part of this crate's public API. No code change.
ifc-approval
Added
DateTimeInput: an approval time as IFC4/IFC4X3IfcDateTimetext or an IFC2X3IfcDateTimeSelectrecord (From<&str>,From<EntityId>).ApprovalError::NotInSchema,StructuredValue,EntityNotInSchema,AuthoringNotInSchemaandAuthoringValueType, and projections'release().SchemaVersionis re-exported.
Changed (breaking)
The approval views read every attribute by name in the model's declared release (#212). They read the IFC4 positions from every file, so an IFC2X3
IfcApprovalanswered its description as the identifier and its date record as the name, and an IFC2X3IfcApprovalRelationshipswapped its ends.ApprovalViewbinds the header (IFC2X3, IFC4 or IFC4X3; none reads as IFC4) and a lookup refuses IFC4X1, IFC4X2, unknown and multiple schemas. An attribute the release does not declare isNotInSchema(IFC2X3IfcApprovalhas noRequestingApproval,GivingApproval, orStatus,LevelandQualifierunder those names, which are not documented as itsApprovalStatus,ApprovalLevelandApprovalQualifier), andtime_of_approvalon IFC2X3'sApprovalDateTimerecord isStructuredValuewith the record id. IFC2X3RelatedApprovalis read as a one-elementrelated_approvals. IFC4 and IFC4X3 answers are unchanged.create_approval,relate_approvalsandrelate_resource_approvalbind the declared release and lay their records out by name (#212). In IFC2X3 anIfcApprovalhas seven attributes and requiresIdentifier,NameandApprovalDateTime(the IFC4TimeOfApproval) as a date record; anIfcApprovalRelationshiptakes exactly one related approval and requiresName;IfcResourceApprovalRelationshipdoes not exist (EntityNotInSchema). A value the release does not declare isAuthoringNotInSchema(formerlyAuthoringInvalid) and one it cannot holdAuthoringValueType. IFC4 and IFC4X3 records are unchanged.ApprovalDraft::time_of_approvalisOption<DateTimeInput>; the setter takesimpl Into<DateTimeInput>, so.time_of_approval("…")still compiles.Approval::try_new,ApprovalRelationship::try_new,ResourceApprovalRelationship::try_newandApprovalAssignment::try_newtake theSchemaVersionto read against, refusing IFC4X1, IFC4X2 and an entity the release does not declare.ApprovalView::newis no longerconst.ApprovalDraft,ApprovalRelationshipDraft,ResourceApprovalDraftandApprovalAssociationDraftare#[non_exhaustive](#214). Struct literals no longer compile outside the crate: build them withApprovalDraft::new(),ApprovalRelationshipDraft::new(relating_approval, related_approvals),ResourceApprovalDraft::new(related_resources, relating_approval)andApprovalAssociationDraft::new(global_id, related_objects, relating_approval)plus field-named setters. Fields stay public.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-author
Changed (breaking)
PersonDraft,OrganizationDraft,ApplicationDraftandOwnerHistoryDraftare#[non_exhaustive](#214), so a later release can add a field without another break. Struct literals no longer compile outside the crate: build each withnew(...)and field-named setters,PersonDraft::new(),OrganizationDraft::new(name),ApplicationDraft::new(developer, version, full_name, identifier)andOwnerHistoryDraft::new(owning_user, owning_application, creation_date)(then e.g..change_action("ADDED")). Fields stay public.
Changed
- Links no bundled schema table itself: every entry point takes the
Schemafrom the caller. A consumer that used a table through this crate's dependency (ifc_schema::ifc4()) enables it on its ownifc-schemadependency (default features bundle every release). - A type declaration form
ifc-schemaadds later resolves as unresolved (no refusal on shape, no form claim) instead of failing to compile; followsifc_schema::TypeKindbecoming#[non_exhaustive].
ifc-classification
Changed (breaking)
ClassificationHierarchyandEffectiveClassificationsare#[non_exhaustive].- Every public draft is
#[non_exhaustive], so struct literals no longer compile outside the crate. Each gains a constructor taking its required fields and one builder setter per other field, named after the field and taking the unwrapped value (.source("NBS")setsSome):ClassificationDraft::new(name)DocumentDraft::new(identification, name)LibraryDraft::new(name)AssociationDraft::new(global_id, related_objects)ExternalReferenceRelationshipDraft::new(relating_reference, related_resources)ClassificationReferenceDraft::new(),DocumentReferenceDraft::new()andLibraryReferenceDraft::new(), which now also deriveDefault
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). UnsupportedSchemareads "a release this crate has no verified layout for" instead of "no bundled schema table".- A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-constraint
Added
DateTimeInput: a creation time as IFC4/IFC4X3IfcDateTimetext or an IFC2X3IfcDateTimeSelectrecord (From<&str>,From<EntityId>).ConstraintError::NotInSchema,StructuredValue,EntityNotInSchema,AuthoringNotInSchemaandAuthoringValueType, and projections'release().SchemaVersionis re-exported.
Changed (breaking)
The constraint views read every attribute by name in the model's declared release (#212). They read IFC4 positions, types and SELECTs from every file.
ConstraintViewbinds the header (IFC2X3, IFC4 or IFC4X3; none reads as IFC4) and a lookup refuses IFC4X1, IFC4X2, unknown and multiple schemas. An attribute the release does not declare isNotInSchema: IFC2X3IfcMetric.ReferencePath, and IFC2X3IfcObjective.LogicalAggregator, whose slot holdsResultValues, anIfcMetric, which was reported as a malformed operator. An IFC2X3CreationTimerecord isStructuredValuewith the record id instead ofInvalidValue; IFC2X3's singleBenchmarkValuesmetric is a one-element list;DataValueis checked against the release's ownIfcMetricValueSelectand is required in IFC2X3. IFC4 and IFC4X3 answers are unchanged.create_metric,create_objective,relate_resource_constraintandcreate_referencebind the declared release and lay their records out by name (#212). In IFC2X3 a metric has ten attributes and requiresDataValue,ReferencePathisAuthoringNotInSchema, an objective takes exactly oneIfcMetricbenchmark and no logical aggregator, textCreationTimeisAuthoringValueType, andIfcResourceConstraintRelationshipandIfcReferenceareEntityNotInSchema. Enumeration tokens are checked against the release's enumeration (IFC2X3 lacks, for example,INCLUDESandMODELVIEW). IFC4 and IFC4X3 records are unchanged.ConstraintBaseDraft::creation_timeisOption<DateTimeInput>; the setter takesimpl Into<DateTimeInput>, so.creation_time("…")still compiles.Metric::try_new,Objective::try_new,ResourceConstraintRelationship::try_newandConstraintAssignment::try_newtake theSchemaVersionto read against, refusing IFC4X1, IFC4X2 and an entity the release does not declare.ConstraintView::newis no longerconst. A value for an attribute the release does not declare, whichassociate_constraintcould not hit, isAuthoringNotInSchemarather thanAuthoringInvalid.Every public draft is
#[non_exhaustive], so struct literals no longer compile outside the crate. Each gains a constructor taking its required fields and one builder setter per other field, named after the field and taking the unwrapped value (.description("…")setsSome):ConstraintBaseDraft::new(name, grade)MetricDraft::new(base, benchmark)ObjectiveDraft::new(base, qualifier)ResourceConstraintDraft::new(relating_constraint, related_resources)ConstraintAssociationDraft::new(global_id, related_objects, relating_constraint)ReferenceDraft::new(), which now also derivesDefault
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-control
Changed (breaking)
ControlDraftandControlAssignmentDraftare#[non_exhaustive](#214). Struct literals no longer compile outside the crate: build them withControlDraft::new()andControlAssignmentDraft::new(global_id, control, related_objects)plus field-named setters (.name("Permit")). Fields stay public.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-cost
Added
- IFC2X3 cost schedules can carry their dates (#214):
DateTimeValuecarries a date either as IFC4/IFC4X3IfcDateTimetext (DateTimeValue::Text,From<&str>) or as one of the IFC2X3IfcDateTimeSelectrecords (Date(CalendarDate),Time(LocalTime),DateAndTime(CalendarDate, LocalTime)). In IFC2X3create_cost_schedule_with_owner_historystages theIfcCalendarDate,IfcLocalTimeandIfcDateAndTimerecords forSubmittedOnandUpdateDateand references them, only once the schedule itself is accepted. Record forms are checked against the schema's rules before anything is staged (IfcValidCalendarDatewithIfcLeapYear,IfcValidTime, and the component ranges), refused withInvalidValue. The types duplicateifc-schedule's, since sibling domain crates may not depend on each other. - Every draft has a constructor and a setter per optional field, named after it:
CostItemDraft::new(global_id),CostScheduleDraft::new(global_id),NestingDraft::new(global_id, parent, children),ScheduleAssignmentDraft::new(global_id, schedule, items),QuantityDraft::new(kind, name, value);CostValueDraftkeepsmonetaryandDefaultand gains setters.
Fixed
create_cost_schedule_with_owner_historyno longer refuses every dated IFC2X3 schedule (#214): a record form is written as theIfcDateTimeSelectIFC2X3 declares. In IFC4 and IFC4X3 a record form is refused withAuthoringValueType, as text is in IFC2X3.
Changed (breaking)
create_cost_value,create_monetary_unitandcreate_currency_relationshipbind the model's declared release and lay their records out by attribute name (#213). They wrote the IFC4 layout into every model. In IFC2X3 anIfcCostValuehas eight attributes and requiresCostType, written fromcategory(IFC4 renamedCostTypetoCategory); a composed value (ArithmeticOperator,Components) is refused withAuthoringNotInSchema.IfcMonetaryUnit.Currencyis anIfcCurrencyEnumenumerator in IFC2X3 (a label it does not list isAuthoringValueType) and anIfcLabelfrom IFC4 on. An IFC2X3IfcCurrencyRelationshiphas five attributes and requiresRateDateTimeas anIfcDateAndTimerecord. IFC4 and IFC4X3 records are unchanged.create_monetary_unittakes the&Model(create_monetary_unit(tx, model, currency)), which it needs to bind the release.CostValueDraft::applicable_dateandfixed_until_dateareOption<DateTimeValue>, andcreate_currency_relationshiptakesrate_date_time: Option<DateTimeValue>: IFC4 and IFC4X3 text as before ("2026-01-01".into(); the draft setters takeimpl Into<DateTimeValue>), or an IFC2X3 record form, which the writer stages. A form the release does not declare is refused withAuthoringValueType.CostView::schedulesreturnsResult<impl Iterator<Item = CostSchedule>, CostError>and everyCostScheduleaccessor reads its attribute by name in the model's declared release (#212). The IFC4 positions misread an IFC2X3IfcCostSchedule:PreparedByas the predefined type, theSubmittedOnrecord as the status andSubmittedByas the identification.identificationreads IFC2X3'sID, which IFC4 renamed. A header declaring IFC4X1, IFC4X2 or an unknown release isCostError::UnsupportedSchema, severalCostError::MultipleSchemas(new variants); no header reads as IFC4.CostSchedule::submitted_onandupdate_datereturnOption<AuthoredDateTime>: IFC4/IFC4X3 text or the IFC2X3 date record (AuthoredDateTime::Record), neverNonefor a stated IFC2X3 date.CostSchedule::new(id, entity, release)takes the release to read against and returnsResult, refusing IFC4X1 and IFC4X2.CostValueDraft,CostItemDraft,CostScheduleDraft,NestingDraft,ScheduleAssignmentDraftandQuantityDraftare#[non_exhaustive]: build them with their constructors and setters instead of a struct literal. Their fields stay public to read and assign.CostScheduleDraft::submitted_onandupdate_dateareOption<DateTimeValue<'a>>(wereOption<&'a str>); text converts with.into()or through the setters, and IFC4/IFC4X3 output is unchanged.#[non_exhaustive]on the public enums and result structs a later release could extend:CostItemType,CostScheduleType,CostValueKind,QuantityKind,ArithmeticOperator,UnitBasisandConsistency. Amatchoutside the crate needs a wildcard arm, and the structs can no longer be built outside it.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-element-type
Added
TypeDraftfields for the type-specific attributes IFC4 and IFC4X3 require (#214):operation_typeanduser_defined_operation_type(IfcDoorType),partitioning_typeanduser_defined_partitioning_type(IfcWindowType),parameter_takes_precedence(both),event_trigger_typeanduser_defined_event_trigger_type(IfcEventType), andassembly_place(IfcFurnitureType, IFC2X3 too). Tokens are checked against the bound release's enumeration (Invalidotherwise); a value for an attribute the type does not declare is refused withAuthoringNotInSchema;USERDEFINEDevent_trigger_typewithout a non-blankuser_defined_event_trigger_typeis refused (CorrectEventTriggerType). These four types can now be authored in every release that declares them.TypeDraft::newandSupertypeDraft::new, and one builder setter per field, named after it (TypeDraft::new().name("Beam").tag_or_long_description("B-1")).
Fixed
create_type(andcreate_supertype), which take no model, no longer write$into an attribute IFC4X3 requires: a required attribute left unset is refused withAuthoringRequired, staging nothing, as the model-bound writers already did (#214).
Changed (breaking)
TypeDraftandSupertypeDraftare#[non_exhaustive]: struct literals outside the crate no longer compile. Usenew()(ordefault()) and the setters; the fields stay public for reading and assignment.create_typerefusesIfcDoorType,IfcWindowType,IfcEventTypeandIfcFurnitureTypewithout their required type-specific attribute, where it wrote$before.Slot6,Family,ElementTypeandSupertypeKindare#[non_exhaustive]: amatchonFamilyorSlot6needs a wildcard arm, and catalogue rows can no longer be built by struct literal outside the crate (use the generated constants).
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-geometry
Added
- Lowering an
IfcTriangulatedFaceSetnow attaches texture coordinates from itsIfcIndexedTriangleTextureMapas a corner-indexed attribute channel nameduv(lower::tessellated::UV_CHANNEL), one(s, t)per triangle corner (#30). Positions stay shared, so the mesh stays closed. A shorterTexCoordIndexleaves trailing trianglesUNMAPPED; an omitted one adds no channel; a longer one, or an index outside the texture vertices, is an error. A second map on the same face set becomesuv1, and so on.
Known limits
IfcIndexedPolygonalTextureMap(IFC4X3) is not lowered.
Changed
- Breaking: requires Axiolid 0.3 (
axiolid-mesh0.3 addsAttributeChannel::corner_indices).
ifc-georef
Changed
- Breaking: requires Axiolid 0.3.
ProjectToMap::transformis anaxiolid_core::Transform3, so the major Axiolid version is part of this crate's public API. No code change.
ifc-material
Changed (breaking)
- Views and authoring bind to the release the model's header declares (#77). Every slot position comes from that release's bundled
ifc-schematable (IFC2X3 TC1, IFC4 ADD2 TC1 or IFC4X3 ADD2) instead of IFC4 constants, so an IFC2X3 model is no longer read with IFC4 slots. Behaviour for an IFC4 model, and for an in-memory model whose header declares no schema (bound to IFC4 as before), is unchanged, except where listed below. Breaking, because the same call now answers differently for IFC2X3 and IFC4X3 models:- an accessor for an attribute the release does not declare returns
MaterialError::NotInSchemainstead ofOk(None). For IFC2X3:Material::{description, category},MaterialLayer::{name, description, category, priority},MaterialLayerSet::descriptionandMaterialLayerSetUsage::reference_extent. A typed absence rather thanNone, becauseNoneclaims the file left the value unset; - a record the release cannot instantiate -- an
IfcMaterialConstituent(Set),IfcMaterialProfile*,IfcMaterialLayerWithOffsetsorIfcMaterialRelationshipin an IFC2X3 model, or IFC2X3's abstractIfcMaterialProperties-- is refused withMaterialError::EntityNotInSchemaby its accessors,resolve_material_selectandassigned_material; - an IFC2X3
IfcMaterialLayer.LayerThicknessof zero is invalid (IfcPositiveLengthMeasure; IFC4 relaxed it toIfcNonNegativeLengthMeasure); - the
IfcRelDefinesByTypefallback admits the concreteIfcTypeObjectsubtypes of the model's release, so IFC4X3 no longer accepts theIfcDoorStyle/IfcWindowStyleit removed. The generated IFC4 list is gone; - a header declaring several schemas fails every read and write with
MaterialError::MultipleSchemas, and one declaring an unknown schema withMaterialError::UnsupportedSchema.
- an accessor for an attribute the release does not declare returns
create_materialtakes the&Modelit writes into and returnsMaterialResult<EntityId>, like every other authoring function: without the model it cannot know the release, and IFC2X3IfcMaterialhas noDescriptionorCategoryslot to write.- Authoring writes the release's layout: IFC2X3 gets the short
IfcMaterial,IfcMaterialLayer,IfcMaterialLayerSetandIfcMaterialLayerSetUsagerecords. A draft value the release cannot hold (an IFC2X3 layer name, category or priority, a layer set description, a usage reference extent, a material description or category) is refused withMaterialError::AuthoringNotInSchemarather than dropped; an entity it lacks is refused withMaterialError::EntityNotInSchema; and a required attribute left unset -- the IFC2X3IfcRelAssociatesMaterial.OwnerHistory-- withMaterialError::AuthoringRequired, soassociate_materialrefuses an IFC2X3 model. Nothing is staged on refusal. - IFC4X3 is read against its own table. Its material entities keep the IFC4 layouts; the one rename,
IfcMaterialRelationship.ExpressiontoMaterialExpressionat the same position, is resolved by name. ifc-schemais now a production dependency.
Fixed
associate_material,create_material_propertiesand layer-set members accept exactly what the release's schema accepts:RelatingMaterialany instantiableIfcMaterialSelectmember (anIfcMaterialLayertoo; IFC2X3 has no constituent or profile sets),MaterialanyIfcMaterialDefinition, and noIfcMaterialLayerWithOffsetsin an IFC2X3 set.
Added
material_schema(model),MaterialView::schema()and aschema()accessor on every projection report the bound release;SchemaVersionis re-exported.try_from_view(view, id)on every projection looks a record up and binds it to the model's release.try_newhas no header and keeps reading IFC4.associate_material_with_owner_historytakes a caller-suppliedIfcOwnerHistory, which IFC2X3 requires; none is ever invented.MaterialError::{MultipleSchemas, UnsupportedSchema, NotInSchema, EntityNotInSchema, AuthoringNotInSchema, AuthoringRequired}.MaterialView::constituent_fraction_diagnostic: an opt-in policy check that anIfcMaterialConstituentSet's fractions describe one whole (#103). It returns aConstituentFractionDiagnosticwhen every constituent states a fraction but the sum is further from 1 than the caller's tolerance (SumNotOne), or when stated and missing fractions are mixed (PartiallyStated). IFC4 declares no WHERE rule on the sum, so this is never a decode error and never normalises: the accessors keep returning the authored fractions.
ifc-model
Changed
tests/package_architecture.rsenumerates the allowed bridge-to-bridge dependencies (BRIDGE_EDGES: onlyifc-geometry -> ifc-alignment) instead of allowing any bridge pair, and a new test requires the manifests to match that list exactly (#143, ADR 0003 amendment 2026-09-28). No crate API changes.
Changed (breaking)
ConflictandStopare#[non_exhaustive]: a match needs a wildcard arm, so a new commit-conflict or walk-stop reason is not a breaking change.Appliedis#[non_exhaustive]: read its fields; it can no longer be built with a struct literal outside the crate.
ifc-occurrence
Added
OccurrenceDraftfields for what IFC2X3 TC1 requires of a few classes (#214):shape_type(IfcRamp,IfcRoof,IfcStair),nominal_diameterandcross_section_area(IfcReinforcingBar,IfcTendon),bar_role(IfcReinforcingBar), andlongitudinal_barsandtransverse_bars(IfcReinforcingMesh), each a newMeshBarsof nominal diameter, cross-section area and spacing. These IFC2X3 records can now be authored withcreate_with_owner_history; IFC4 and IFC4X3, which declare the measuresOPTIONAL, write them when given. A value for an attribute the bound release does not declare on the class is refused withAuthoringNotInSchema.OccurrenceError::UnknownToken(aShapeTypeorBarRoleoutside the release's enumeration),InvalidMeasure(a non-positive or non-finiteIfcPositiveLengthMeasure, a non-finiteIfcAreaMeasure) andTypeClassNotInSchema(the bound release pairs no type class with the occurrence, such as an IFC2X3IfcStair), appended.OccurrenceErrorimplementsDisplayandstd::error::Error.OccurrenceDraft::newand one builder setter per field, named after it.Occurrence::ifc4_type_classandOccurrence::ifc2x3_type_class: the type class IFC4 ADD2 TC1 and IFC2X3 TC1 pair with each class, generated from their EXPRESS sources byscripts/gen-occurrences.py.
Fixed
- The occurrence-to-type pairing follows the declared release (#214). It was IFC4X3's
CorrectTypeAssignedin every release, so an IFC2X3IfcDoortyped by anIfcDoorStylewas refused; IFC2X3 now pairs doors and windows withIfcDoorStyleandIfcWindowStyleand every other class with the later releases' type class where IFC2X3 declares it, and IFC4 uses its own rules (IFC4'sIfcTransformerrule names the undeclaredIFCTRANFORMERTYPE, an erratum recorded as written). The referenced type is compared withTYPEOFsemantics, subtypes included.
Changed (breaking)
OccurrenceDraftis#[non_exhaustive]: struct literals outside the crate no longer compile. Usenew()(ordefault()) and the setters; the fields stay public.Occurrence, the generated catalogue row, is#[non_exhaustive]and has two new fields; it can no longer be built by struct literal outside the crate (use the generated constants).- An IFC2X3 or IFC4
typed_byis checked against that release's pairing: a class IFC2X3 pairs with nothing (its type class undeclared there) is refused withTypeClassNotInSchemawhere it was checked against the IFC4X3 class, andWrongTypeClass.expectednames the release's class.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-properties
Added
exact_unit(model, measure_type, explicit_unit)resolves a measure's effective unit to an exact SI conversion (#53). It uses the explicit unit, otherwise the project default, and returnsvalue_si = value * scale + offsetwith the SI dimensional exponents. Conversion-based chains, derived units, the gram, and degrees Celsius are resolved. Count and ratio measures get a dimensionless answer.- It refuses rather than guesses (
ExactUnitError):- unknown prefixes, unit names or measure types
- duplicate or missing project units
- cyclic or over-deep conversion chains
- a unit whose name, declared
Dimensionsor factor unit contradicts its type - an explicit unit of the wrong type
- offset units
- It binds to the declared release like
exact_propertyand reads that release'sIfcDimensionsForSiUnit/IfcCorrectDimensions. They differ: IFC2X3 TC1's farad fails its own capacitance check, and it is reported as such. UnitKind::Conversion::offsetkeepsIfcConversionBasedUnitWithOffset.ConversionOffset, which was dropped.
Changed (breaking)
UnitKind::Si::prefix_exponentis nowOption<i32>. An unrecognised prefix isNoneinstead of0, which read.BOGUS.as "unprefixed" and scaled by 1.0.UnitKind::si_scaleraises the prefix to the unit's power:MILLI SQUARE_METREis 1e-6 (was 1e-3), andMILLI CUBIC_METREis 1e-9. It isNonefor an unknown prefix.UnitKind::Conversiongains theoffsetfield, so exhaustive patterns must name it or use...
ifc-resource
Changed (breaking)
ActorRoleDraft,PostalAddressDraft,TelecomAddressDraft,ActorDraft,AssetDraft,InventoryDraft,AppliedValueDraft,ResourceDraft,ResourceTimeDraft,AllocationDraftand the resourceNestingDraftare#[non_exhaustive](#214). Struct literals no longer compile outside the crate: build each withnew(...)and field-named setters. New constructors:ActorRoleDraft::new(role),PostalAddressDraft::new(),TelecomAddressDraft::new(),ActorDraft::new(global_id, the_actor),AssetDraft::new(global_id),InventoryDraft::new(global_id)andAppliedValueDraft::new(), each with a setter per remaining field. The drafts that already had builders keep them unchanged. Fields stay public where they were.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3. - Enumeration checks treat a type declaration form
ifc-schemaadds later as not matching; followsifc_schema::TypeKindbecoming#[non_exhaustive].
ifc-schedule
Added
create_lag_time_in(tx, model, name, lag_value, duration_type): the model-boundcreate_lag_time. IFC2X3 declares noIfcLagTimeand is refused withEntityNotInSchema, nothing staged; IFC4 and IFC4X3 stage exactly whatcreate_lag_timestages.create_lag_timedocuments that it is for IFC4 and IFC4X3 only (#211).- IFC2X3 work plans and work schedules can be authored (#214):
DateTimeValuecarries a date either as IFC4/IFC4X3IfcDateTimetext (DateTimeValue::Text,From<&str>) or as one of the IFC2X3IfcDateTimeSelectrecords (Date(CalendarDate),Time(LocalTime),DateAndTime(CalendarDate, LocalTime)). In IFC2X3create_work_control_with_owner_historystages theIfcCalendarDate,IfcLocalTimeandIfcDateAndTimerecords and references them, only once the work control itself is accepted. Record forms are checked against the schema's rules before anything is staged (IfcValidCalendarDatewithIfcLeapYear,IfcValidTime, and the ranges ofIfcMonthInYearNumber,IfcHourInDay,IfcMinuteInHourandIfcSecondInMinute), refused withInvalidValue.CalendarDateandLocalTimeare#[non_exhaustive];LocalTimedoes not yet carryZoneorDaylightSavingOffset, which are written$. - IFC2X3 sequences can be authored (#214):
TimeLag::Secondsis the IFC2X3IfcRelSequence.TimeLag : IfcTimeMeasure,TimeLag::LagTimethe IFC4/IFC4X3IfcLagTimereference. A non-finite lag is refused withInvalidValue. ProcedureDraft::user_defined_procedure_type, written as the IFC2X3IfcProcedure.UserDefinedProcedureType(#214), so aUSERDEFINEDIFC2X3 procedure can be authored.- Every draft has a
newconstructor taking its required fields (TaskDraft::new(global_id),WorkControlDraft::new(global_id, creation_date, start_time),EventDraft::new(global_id),ProcedureDraft::new(global_id),RecurrenceDraft::new(recurrence_type),EventTimeDraft::new(),TaskTimeDraft::new()) and a setter per other field, named after it, asifc-resource's drafts are built.
Fixed
create_work_control_with_owner_history,create_sequence_with_owner_historyandcreate_procedure_with_owner_historyno longer refuse the IFC2X3 records the drafts could not carry (#214).IfcProcedure.WR4is enforced instead: an IFC2X3USERDEFINEDprocedure without a non-blankuser_defined_procedure_typeis refused withInvalidValue.- The release-bound writers refuse a
SchemaVersionthis build carries no table for withUnsupportedSchemainstead of panicking.
Changed (breaking)
- The task, work-control and sequence readers bind the model's declared release and read every attribute by name from its table (#212). They read IFC4 slot constants from every file, so an IFC2X3 task answered its
Statusas the long description,WorkMethodas the status andPriorityas the milestone flag, and an IFC2X3 work plan or schedule itsWorkControlTypeas the predefined type. The header binds IFC2X3, IFC4 or IFC4X3 (none reads as IFC4); IFC4X1, IFC4X2, unknown and multiple schemas are refused with the newScheduleReadError. IFC4 and IFC4X3 answers are unchanged. tasks,work_plans,work_schedules,sequences,predecessors_of,successors_of,start_tasks,end_tasks,tasks_of_scheduleandsubtasks_ofreturnResult<_, ScheduleReadError>;find_cyclereturnsResult<Option<SequenceCycle>, ScheduleReadError>;downstream_ofandexecution_orderreturnScheduleReadError::Cycle(SequenceCycle)for a loop instead of a bareSequenceCycle.Task::new(id, entity, release)andWorkControl::new(id, entity, release)take theSchemaVersionto read against and returnResult, refusing IFC4X1 and IFC4X2;WorkControl::newisOk(None)for another entity.Task::releaseandWorkControl::releasereport the binding.- An attribute IFC2X3 does not declare reads as
None:Task:: long_description,predefined_typeandtask_time_ref, andWorkControl::predefined_type.Task::identificationreads IFC2X3'sTaskIdandWorkControl::identificationitsIdentifier, which IFC4 promoted toIdentification.WorkControl::work_control_typereads IFC2X3'sWorkControlType, which is not aliased toPredefinedType. WorkControl::creation_date,start_timeandfinish_timereturnOption<AuthoredDateTime>(IFC4/IFC4X3 text, or the IFC2X3IfcDateTimeSelectrecord), anddurationandtotal_floatreturnOption<AuthoredDuration>(IFC4/IFC4X3 text, or the IFC2X3IfcTimeMeasure), instead ofNonefor a stated IFC2X3 value.Sequencegainstime_lag_measure, IFC2X3'sIfcRelSequence.TimeLag(anIfcTimeMeasureon the relationship);lagstays the IFC4/IFC4X3IfcLagTime.TaskDraft,WorkControlDraft,EventDraft,EventTimeDraft,RecurrenceDraft,ProcedureDraftandTaskTimeDraftare#[non_exhaustive]: build them withnewand the setters instead of a struct literal. Their fields stay public to read and assign.WorkControlDraft::creation_dateandstart_timeareDateTimeValue<'a>(were&'a str) andfinish_timeisOption<DateTimeValue<'a>>(wasOption<&'a str>). The plaincreate_work_controlrefuses a record form withInvalidValue; with text its output is unchanged.create_sequence_with_owner_historytakestime_lag: Option<TimeLag>(wasOption<EntityId>); wrap anIfcLagTimeid inTimeLag::LagTime. The plaincreate_sequenceis unchanged.ProcedureDrafthas the newuser_defined_procedure_typefield; the plaincreate_procedure, which writes IFC4/IFC4X3, refuses a value for it withInvalidValue, and the release-bound writer refuses it in IFC4 and IFC4X3 withAuthoringNotInSchema.#[non_exhaustive]on the public enums and result structs a later release could extend:SequenceType,DurationType,TaskTimeAnomaly,WorkControlKind,WorkTimeRole,RecurrenceType,Lag,Sequence,SequenceCycle,EventTime,RecurrenceandWorkTime. Amatchoutside the crate needs a wildcard arm, and the structs can no longer be built outside it.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-schema
Changed (breaking)
- Artifact format 3 records the facts above; format 1 and 2 artifacts still decode, with those facts empty.
- A nested aggregate attribute's
type_nameis its innermost element type (IfcLengthMeasureforLIST OF LIST OF IfcLengthMeasure), where the old extractor recorded the inner keywordLIST. - The
expressandgenerationfeatures useopenbim-step=0.10.0(the runtime links none). - One cargo feature per bundled release (#112):
ifc2x3,ifc4,ifc4x1,ifc4x2andifc4x3, all indefault, so a default build bundles every release as before. Each accessor (ifc2x3(),ifc4(), ...) exists only with its feature.ifc4used to ship all bundled tables; it now ships IFC4 only, so a build withdefault-features = false, features = ["ifc4"]loses the other releases -- name them, or keep defaults. for_versionreturnsResult<&Schema, NotBundled>instead ofOption<&Schema>, and exists in every build.Err(NotBundled)means a recognised release whose feature is off; an unknownFILE_SCHEMAtoken is stillNonefromSchemaVersion::from_header_token, so the two cases stay distinguishable.write_structural_catalogandwrite_direct_structural_catalogexist in every build and return anio::ErrorKind::Unsupportederror wrappingNotBundledfor a release that is not compiled in, instead of panicking.artifact_decode_schemaandBundledSchemaErrorneed the newartifactfeature (enabled by every release feature) instead ofifc4.SchemaVersionis#[non_exhaustive], derivesHash, and gainsIfc4x1andIfc4x2(#33). Amatchon it needs a wildcard arm; a consumer should refuse a release it has not verified, never alias it to a neighbour.write_structural_catalogandwrite_direct_structural_catalogaccept the new versions.ifc-schemaowns its schema types.Attribute,EntityDef,TypeDef,TypeKindand the newly exportedWhereRuleare defined here instead of re-exported fromopenbim_step::express, and all five are#[non_exhaustive]: construct them withAttribute::new,EntityDef::new,TypeDef::new,WhereRule::newand the builder methods (with_supertype,with_attribute,with_derived,with_where_rule,abstract_entity,optional,aggregate), and give everymatchonTypeKinda wildcard arm. Field names,supertype(),is_derived()andis_defined()are unchanged. Rationale: anopenbim-steprelease no longer ripples into this crate's public API, and later facts about a declaration (aggregate bounds, INVERSE, UNIQUE) can be added as fields without another break.openbim-stepis an optional dependency, linked only by the newexpressfeature and bygeneration. The bundled tables decode straight into the owned types; the default build no longer links a parser.Schema::from_expressandSchema::from_express_bytesrequire the newexpressfeature.- Removed:
Schema::from_parsed(ParsedSchema)(useSchema::new(name, entities, types)),Schema::graph()(theopenbim_step::SchemaGraphit returned is no longer held;Schemaanswers the same queries itself), and theexpressmodule with itsparse/ParsedSchemare-exports (useopenbim_step::expressdirectly). EntityDefno longer carriesredeclared/is_redeclared(). No bundled table ever recorded explicit redeclarations (the artifact format drops them), so they were always empty forifc2x3(),ifc4()andifc4x3().artifact_decode_schemareturns aSchemaandartifact_encode_schema(generation) takes one, instead ofopenbim_step::express::ParsedSchema.
Added
- Aggregate bounds, nested aggregation, INVERSE and UNIQUE (#111):
Attribute::aggregation(levels outermost first, each anAggregationwithAggregateKind, lower and upperBound,unique,optional_elements),EntityDef::inverses(InverseAttribute) andEntityDef::unique_rules(UniqueRule), with builders. Additive: the types were already#[non_exhaustive]. All five bundled tables are regenerated with them: 115/153/158/160/165 INVERSE and 17/4/4/4/4 UNIQUE declarations for IFC2X3/IFC4/IFC4X1/IFC4X2/IFC4X3, pinned by tests. NotBundled,SchemaVersion::is_bundled()andSchemaVersion::feature_name().- IFC4X1 FINAL and IFC4X2 FINAL (#33): bundled tables
data/ifc4x1-final.bin(801 entities, 400 types) anddata/ifc4x2-final.bin(816 entities, 407 types), generated from the official EXPRESS files like the other three; accessorsifc4x1()andifc4x2();for_versionreturns them; header tokensIFC4X1andIFC4X2(the files' ownSCHEMAnames); release idsIFC4X1_FINALandIFC4X2_FINAL. Tests pin that both counts differ from IFC4 and IFC4X3 and that each carries its own release's entities. SchemaVersion::ALL, every known version oldest first.Schema::new,Schema::entities()andSchema::types()(declarations in source order),PartialEq/EqforSchema, and theBundledSchemaErrorexport.
Unchanged
- The bundled artifacts are byte-identical: regenerating all three with the ported generator reproduces the committed files, and
FORMAT_VERSIONstays 2.
Changed
- Requires
openbim-step0.7.0, matchingifc-step. Both pin the parser exactly, so the pair must move together.openbim-step0.6 replacedEntityDef::supertype(a field) withsupertypesplus asupertype()accessor for multiple inheritance; IFC schemas are single-inheritance, so the serialized artifact is unchanged.
ifc-spatial
Added
SpatialDraft::interior_or_exterior: the IFC2X3IfcSpace. InteriorOrExteriorSpace(IfcInternalOrExternalEnum), which that release requires, socreate_spatial_element_with_owner_historycan author an IFC2X3 space (#214). IFC4 and IFC4X3 do not declare the attribute: a value there, or on a container other than a space, is refused withAuthoringNotInSchema, a token outside IFC2X3's enumeration withAuthoringValueType, and the plain IFC4/IFC4X3create_spatial_elementrefuses it withAuthoringNotInSchemainstead of dropping it.create_project_with_owner_historytakes the project's representation contexts, so an IFC2X3IfcProject, which requiresRepresentationContexts(andUnitsInContext), can be authored (#214). Each context must resolve in the model or on the transaction (MissingReference), be anIfcRepresentationContext(WrongReferenceType), not be anIfcGeometricRepresentationSubContext(IFC2X3WR32, IFC4 and IFC4X3CorrectContext), and not repeat (Invalid); an empty slice leaves the attribute$, which IFC2X3 refuses withAuthoringRequired.- A constructor and builder setters on every draft:
SpatialDraft::new(),FacilityDraft::new(),ExternalSpatialDraft::new()andProjectLibraryDraft::new()start empty;BoundaryDraft::new(space, element, physical_or_virtual, internal_or_external)takes the four attributes the schema requires. Each optional field has a setter of the same name taking the unwrapped value (SpatialDraft::new().name("L1") .composition("ELEMENT")), followingifc-resource's drafts.
Fixed
- A model whose declared release has no bundled table is refused with
UnsupportedSchemaby the release-bound writers instead of panicking.
Changed (breaking)
assign_to_actor,assign_to_process,connect_with_realizing_elementsandinterfere_elementstake the&Modeland bind its declared release (#213). They wrote records with the wrong number of attributes: seven ofIfcRelAssignsToActor's andIfcRelAssignsToProcess's eight, eight ofIfcRelConnectsWithRealizingElements's nine, and ten forIfcRelInterferesElementswhere IFC4 declares nine. Each record is now laid out by attribute name with the release's own arity (the interference has ten attributes in IFC4X3). IFC4 records gain a trailing$for the first three (ActingRole,QuantityInProcess,ConnectionType) and the IFC4 interference loses its trailing$; IFC4X3 records of the first three gain the same trailing$, and its interference is unchanged. An IFC2X3 model, which requiresOwnerHistory(and declares noIfcRelInterferesElements), and a header binding no single verified release are refused; use the*_with_owner_historyvariants in IFC2X3.SpatialDraft,FacilityDraft,BoundaryDraft,ExternalSpatialDraftandProjectLibraryDraftare#[non_exhaustive]: struct literals and..Default::default()updates no longer compile outside the crate; build them withnewand the setters. Fields stay public for reading and assignment.SpatialDrafthas a new field,interior_or_exterior.create_project_with_owner_history(tx, model, global_id, name, units, representation_contexts, owner_history): the newrepresentation_contexts: &[EntityId]parameter sits beforeowner_history. Pass&[]for the previous IFC4/IFC4X3 record.#[non_exhaustive]on the public read-side and catalogue types a later IFC release could extend:SpaceBoundary,BoundaryPhysicality,BoundaryExposure,Relationship,RelationshipKind,SpatialKind,SpatialNode,BoundaryLevelandFacility. Matches on the enums need a wildcard arm outside the crate, and the structs can no longer be built by literal there.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - The spatial classifier binds no release for an
IFC4X1orIFC4X2header (release()isNone) and answers from the verified tables. - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-step
Changed (lazy loading)
- A strict read loads lazily (ADR 0015). Every record is framed with
openbim_step::scan, parsed from its own span and checked against every rule the IFC conversion enforces -- in parallel on inputs of 4 MB and more -- but no value is built; each entity is decoded on first access by the same conversion the eager read uses. The model keeps the source bytes. On seven real IFC files (18-109 MB) opening is 2.1-4.6x faster at 35-76% less peak memory; decoding everything afterwards on 8 threads is still 1.3-2.5x faster than the eager read at the same memory. Results are unchanged: lazy and eager models (and errors) are identical on the 2,273 files of the parse-limits corpus, and any record that fails validation sends the whole file through the eager reader, so an error is always the eager reader's error.StepReader::eagerkeeps the old behaviour; recovery and reference-check options read eagerly. read_pathreads the file into a buffer the model owns instead of memory-mapping it: a lazy model decodes from its source for its whole lifetime, and a mapping of a file changed meanwhile is undefined behaviour. The mapped read is the newunsafeStepReader::read_path_mapped, whose contract says so.Indexis rebuilt onopenbim_step::scan(breaking). Framing is strict -- junk between records, a missingENDSECor a second appended file are errors instead of being skipped -- and ids are found by binary search instead of a linear scan.Index::scan,materializeandmaterialize_closurereturnResult, andIndex::entityreturnsResult<Option<Entity>>, where they used to swallow decode failures (a failedmaterializereturned an empty model). Subsets carry the file's header instead of a synthetic IFC4 one, andIndex::headerexposes it.- Requires
openbim-step0.8.0.
Added
StepReader::eager,StepReader::read_path_mapped(unsafe) andCodec::read_ownedfor both codec types.
Changed
- Requires
openbim-step0.7.0, matchingifc-schema. Both pin the parser exactly, so the pair must move together. - Reading STEP builds the model from
openbim-step's borrowed events, so each value is allocated once, directly in its model form, instead of once as a parserStringand again as the model'sArc<str>; records are consumed instead of cloned. The model is identical (checked over 2,273 files); reading takes 22-40% fewer instructions and 13-35% fewer cycles on seven real IFC files, with resident memory unchanged.
ifc-structural
Changed (breaking)
MemberConnectionandActivityAssignmentare#[non_exhaustive].- Every public draft is
#[non_exhaustive], so struct literals no longer compile outside the crate. Each gains a constructor taking its required fields and one builder setter per other field, named after the field and taking the unwrapped value (.name("Frame")setsSome;Stringfields takeimpl Into<String>):AnalysisModelDraft::new(global_id, predefined_type)StructuralRootDraft::new(global_id)RelationshipRootDraft::new(global_id)MemberDraft::new(root, kind),ConnectionDraft::new(root, kind)ActionDraft::new(root, applied_load, coordinate_system, kind)ReactionDraft::new(root, applied_load, coordinate_system, kind)LoadGroupDraft::new(global_id, action_type, action_source, kind)ResultGroupDraft::new(global_id, theory_type, is_linear)MemberConnectionDraft::new(root, member, connection)ActivityAssignmentDraft::new(root, relating_element, activity)BoundaryConditionDraft::new()
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3. - Enumeration checks treat a type declaration form
ifc-schemaadds later as not matching; followsifc_schema::TypeKindbecoming#[non_exhaustive].
ifc-style
Added
IndexedTextureMap::triangle_coordinates(triangle_count)resolves anIfcIndexedTriangleTextureMapto(s, t)coordinates for each triangle corner, inCoordIndexorder. Corners, not vertices: one position may carry a different coordinate in each triangle that uses it. A shorterTexCoordIndexcovers only the leading triangles, a longer one is an error, and an omitted one returnsNonebecause the schema does not define it (#30).
Fixed
- Breaking:
IndexedTextureMap::mapsnow returnsVec<EntityId>.MapsisLIST [1:?] OF IfcSurfaceTexture, but it was read as a single reference, so it returned an error on every conforming file, including those this crate writes itself.
ifc-systems
Changed (breaking)
The authoring draft
ClassifiedSystemDraftis#[non_exhaustive]: build it withClassifiedSystemDraft::new()and the settersdescription,object_type,predefined_typeandlong_name. Fields stay public.The bulk readers no longer fall back to the IFC4 table.
systems,ports,zones,spatial_placements,role_inconsistencies,ConnectionGraph::buildandElementRole::ofreturnResult<_, SchemaResolutionError>and refuse a header that binds no verified release: noFILE_SCHEMA(an in-memory model now needs a header), several, IFC4X1/IFC4X2, or a release this build does not bundle.ElementRole::ofreturnsResult<Option<_>, _>.try_zonesis removed:zonesnow has the error channel it added.IFC4X3 is verified for every reader (#215), not only the zone readers:
schema_ofresolves it. Every fixed slot the readers use is pinned against the IFC2X3, IFC4 and IFC4X3 tables, andtests/ifc4x3.rsround-trips systems (includingIfcBuiltSystem), both port attachments, connections, flow roles and spatial placements through IFC4X3 STEP text.SystemAnomalyandRoleInconsistencyare#[non_exhaustive]: a match needs a wildcard arm.The read-side
Connection,Port,System,ZoneandSpatialPlacementare#[non_exhaustive], so a later release's attribute can be added without a breaking change; they can no longer be built with a struct literal outside the crate.
Changed
- Depends on
ifc-schemawith its default features named explicitly (every bundled release), now that the workspace dependency turns them off for the facade's per-release features (#112). - A model whose header declares
IFC4X1orIFC4X2is refused with the existing unsupported-schema error.ifc-schemanow bundles both releases, but no layout here is verified against them, so they are never read as IFC4 or IFC4X3.
ifc-tabular
Changed (breaking)
SeriesDraftandColumnDraftare#[non_exhaustive](#214). Struct literals no longer compile outside the crate: build them withSeriesDraft::new(name, start_time, end_time, data_type, data_origin)andColumnDraft::new()plus field-named setters. Fields stay public.
Changed
- Links no bundled schema table itself: every entry point takes the
Schemafrom the caller. A consumer that used a table through this crate's dependency (ifc_schema::ifc4()) enables it on its ownifc-schemadependency (default features bundle every release).
ifc-template-catalog
Added
corrected_catalog(CatalogEdition::Ifc4x3Add2): an IFC4X3 ADD2 corrected profile whose one patch,NEH-IFC4X3-PSD-0001, addsPset_Stationing.HasIncreasingStation(IfcBoolean). The published ADD2 documentation (6.6.4.10) lists it; the PSD XML the official snapshot is generated from omits it, and the official snapshot is unchanged (#216).PatchOperation::AddPropertyandPatchError::NotAPropertySet.
Changed (breaking)
ValidationIssue,ValidationReport,CatalogDiagnostic,ExportSummary,Advisory,AppliedPatchandUnresolvedApplicabilityare#[non_exhaustive]; they can no longer be built with a struct literal outside the crate.
ifc-validate
This release is breaking (0.2 -> 0.3): Severity gains a variant, Summary gains a field, RuleEntry gains a field, Mismatch gains a variant, three rule ids are renamed, and Budget::max_depth is removed.
Added
Severity::EvaluationErrorandFinding::evaluation_error: an implemented rule that applies to an instance but cannot be decided for it -- the schema tables lack an attribute the rule reads, an operand has a shape the rule cannot reason about, or a target the rule must type-test is absent -- is now reported under the rule's own id instead of being skipped silently (#115).Summary::evaluation_errorscounts them, andReport::is_conformantisfalsewhile any is present. An unset ($) operand is still not an evaluation error: optional operands are guarded by the rules themselves and mandatory ones arestructure.required.missing.- Every rule id the crate emits is pinned by an adversarial pair of fixtures, and an inventory read from the crate's source fails the build when a new id ships without one (#114).
type.entity.expected_reference: a value that is not an entity reference in a slot only a reference can fill -- an entity-typed slot, a member of an aggregate of entities, or a SELECT whose alternatives are all entities -- is reported (#113). A string inIfcRelSequence.RelatingProcessused to get no structure or type finding at all.
Changed
- Breaking:
Severityis#[non_exhaustive]and has the newEvaluationErrorvariant, ordered afterError; exhaustive matches outside the crate must add a wildcard arm. - Breaking:
Summaryis#[non_exhaustive]and has the newevaluation_errorsfield; itsDisplaynow also prints that count. - Breaking:
RuleEntryhas a newreleasesfield, andRuleEntry::applies_tosays whether a schema's release declares the rule. The engine now takes every rule's scope from its registry entry: it runs only under the releases listed there, on the entry's entity and its subtypes (#139). - Breaking: rule ids now always name the entity that declares the rule, with the label the release uses (#139):
IfcRelAssignsToGroupByFactor.NoSelfReferenceisIfcRelAssignsToGroup.NoSelfReference;IfcPhysicalSimpleQuantity.WR21isIfcQuantityLength.WR21; andIfcPolyLoop.WR21is reported only under IFC2X3, with IFC4 and IFC4X3 reporting the same unsupported predicate asIfcPolyLoop.AllPointsSameDim. - Breaking:
type_check::Mismatchis#[non_exhaustive]and has the newExpectedReferencevariant (#113).type_check::check_valuenow checks aggregate members against the element type, and reports a reference written where the declared type resolves to a primitive. IfcExternalReference.WR1readsItemReferenceunder IFC2X3 andIdentificationunder IFC4/IFC4X3, as each release's EXPRESS declares, instead of whichever of the two resolved.
Removed
- Breaking:
Budget::max_depth. Nothing read it: every walk the crate performs is over the bundled schema's type graph, which a file cannot lengthen, and the SELECT walk's own bound is now proven sufficient for every SELECT the bundled schemas use.Budget::max_findingsremains the one file-controlled limit.
Fixed
- Native WHERE rules run on the entities and releases the schema declares them for (#139).
NoSelfReferencenow checks plainIfcRelAssignsToGroup, which was never checked;IfcMaterialLayer.NormalizedPrioritynow checksIfcMaterialLayerWithOffsets; andIfcRelDefinesByProperties.NoRelatedTypeObjectno longer runs under IFC2X3, which does not declare it. Unsupported rules are likewise admitted only under releases that declare them. A new schema-backed test checks every registered id, entity and release set against the normative EXPRESS. - References and values inside aggregates and SELECT slots are type-checked (#113).
structure.reference.wrong_typenow judges every member of an aggregate of entities (a property set inSET OF IfcProduct), including aggregates reached through a type that aliases one;type.select.membernow judges an entity reference against the SELECT's closure, directly and inside aggregates (anIfcWallasRelatingMaterial); andtype.scalar.mismatchnow judges aggregate members (a string inCoordinates) and a reference in a primitive slot. Nested attribute aggregates (LIST OF LIST OF ...) stay unchecked: the schema tables do not retain their element type. A reference to an entity whose type the tables do not declare -- typically a later release's entity -- is no longer reported asstructure.reference.wrong_type; there is no basis for a subtype verdict, andtype.entity.unknownalready warns about it. type.scalar.mismatchnow checks bounded and fixed-width strings. The primitive was read from the trailing token of the resolved type, soSTRING(255)-- IFC4'sIfcLabelandIfcIdentifier-- recognised nothing and every such slot went unchecked; an integer in aNameslot was accepted.
openbim-ifc
Added
ids_of_type_including_subtypes(featureschema): every entity of a type or any of its subtypes, in file order.Model::ids_of_type("IfcElement")returns nothing because no instance is declared as the abstract supertype; this answers the question that call looks like it should. The caller passes theSchema, because the tree differs by version (#32).
Changed
- Breaking: requires
ifc-style0.3.0, re-exported asifc::style.IndexedTextureMap::mapsthere now returnsVec<EntityId>.
openbim-ifc-cli
Added (#464)
select FILE QUERY [--value PATH]... [--format table|json|csv]lists the entities a selector selects, in ascending id order, with their class,GlobalId,Nameand one column per value path. JSON carries the query in canonical form and the grammar version. A selector or path that does not parse, or names a class or attribute the declared release lacks, is refused with the new kindinvalid-selectorand its byte span (exit 2).
Semver: minor (additive).
[0.2.4] - 2026-09-28
ifc-schema
Maintenance release from maint/ifc-schema-0.2; the code is unchanged since 0.2.3.
Added
- A crate README, which is the crates.io page.
ifc-spatial
Added
- A
*_with_owner_historyvariant of everyIfcRootwriter (#202):create_spatial_element,create_project,aggregate,contain,create_external_spatial_element,create_project_library,create_space_boundary,connect_path_elements,create_facility, and inauthoringcover_elements,cover_spaces,declare,define_by_object,serve_buildings,control_flow_element,assign_to_actor,assign_to_product,assign_to_process,assign_to_group_by_factor,assign_to_resource,connect_elements,connect_with_realizing_elements,interfere_elements,void_element,fill_element,project_element,adhere_to_element,position_productsandassociate_profile_def. Each takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot; the id must be in the model or staged on the transaction and be anIfcOwnerHistory, and none is ever invented. Each binds the model's declared release asifc-material(#77),ifc-properties(#191) andifc-classification(#194) do (noFILE_SCHEMAbinds IFC4) and lays the record out by attribute name from that release's table: IFC2X3IfcRelCoversSpaces.RelatingSpaceis written asRelatedSpace, IFC4's name for the same attribute. What the release cannot hold is refused: entities it does not declare (IfcRelDeclares,IfcRelDefinesByObject,IfcRelAssignsToGroupByFactor,IfcRelInterferesElements,IfcExternalSpatialElement,IfcProjectLibraryand the 1st- and 2nd-level space boundaries in IFC2X3;IfcRelAdheresToElement,IfcRelPositions,IfcRelAssociatesProfileDefand the facilities outside IFC4X3), enumeration tokens outside its table, and attributes it requires but the call cannot supply (IFC2X3CompositionType,IfcSpace.InteriorOrExteriorSpace,IfcProject.RepresentationContexts). In IFC4 and IFC4X3 each variant writes the plain writer's record with the reference in slot 1. SpatialAuthoringError::MultipleSchemas,UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringValueType,AuthoringRequired,MissingReferenceandWrongReferenceType, andFacilityError::Authoringwrapping them (#202). Both enums are#[non_exhaustive], so this is not breaking.
Changed
create_space_boundarybinds the model's declared release and lays its record out by attribute name (#202). It wrote the IFC4 layout withOwnerHistory$into every model, and IFC2X3 requires the owner history. Behaviour change for IFC2X3 callers: it now refuses an IFC2X3 model withSpatialAuthoringError::AuthoringRequired { attribute: "OwnerHistory", .. }and stages nothing; usecreate_space_boundary_with_owner_history. It also refuses a header declaring several schemas (MultipleSchemas) or one without a bundled table (UnsupportedSchema), an IFC2X3 1st- or 2nd-level boundary (EntityNotInSchema) and a token the release does not declare, such as IFC2X3EXTERNAL_EARTH(AuthoringValueType). IFC4 and IFC4X3 records are unchanged, slot for slot.
Documented
- The writers that take no model cannot see the release: they write the IFC4/IFC4X3 layout with
OwnerHistory$and are documented as IFC4/IFC4X3 only, pointing to their*_with_owner_historyvariant. Their output is unchanged. Four of them disagree with the release's arity, which the tests pin and the variants do not repeat:assign_to_actorandassign_to_processwrite seven of eight attributes,connect_with_realizing_elementseight of nine, andinterfere_elementsten where IFC4 declares nine.
[0.2.3] - 2026-09-28
ifc-cost
Added
create_cost_item_with_owner_history,create_cost_schedule_with_owner_history,nest_cost_items_with_owner_historyandassign_schedule_items_with_owner_history(#202). Each takes a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. It must be in the model or staged on the transaction (MissingReferenceotherwise) and be anIfcOwnerHistory(WrongReferenceType). None is ever invented. In IFC4 and IFC4X3 the record is the plain writer's with the reference in the optional slot.CostAuthoringError::AuthoringValueTypeandCostAuthoringError::AuthoringRequired, appended to the#[non_exhaustive]enum, so not breaking.
Changed
create_cost_item,create_cost_schedule,nest_cost_itemsandassign_schedule_itemsbind the model's declared release and lay their records out by attribute name from its table (#202), as quantity authoring already did. They wrote the IFC4 layout withOwnerHistory$into every model, which is invalid IFC2X3. Behaviour change for IFC2X3 callers: they now refuse an IFC2X3 model withAuthoringRequired { attribute: "OwnerHistory", .. }and stage nothing; use the*_with_owner_historyvariants there. What IFC2X3 cannot hold is refused, never dropped: anIdentification,PredefinedTypeor cost values on a cost item (AuthoringNotInSchema), a date string where it declares anIfcDateTimeSelect(AuthoringValueType), and a cost schedule without its requiredID(written fromidentification) orPredefinedType(AuthoringRequired). A header declaring several schemas, or one without a bundled table, is refused withMultipleSchemasorUnsupportedSchema. A model withoutFILE_SCHEMAbinds IFC4 as before. IFC4 and IFC4X3 records are unchanged, record for record.
Fixed
assign_cost_quantitiesaccepts every instantiable subtype of the release's ownCostQuantitiesdeclaration (IfcPhysicalQuantity), read from its table instead of a fixed list (#203). An IFC4X3IfcQuantityNumberwas refused. IFC4 still refuses it, as IFC4 does not declare it. The slot is found by name, and an IFC2X3 cost item, which has noCostQuantities, is refused withAuthoringNotInSchemainstead of written past its five attributes. A header that binds no single known release is refused as above.
ifc-model
Added
- Lazily decoded entities (ADR 0015). A codec builds a model with
Model::with_source(Arc<dyn EntitySource>)and registers each entity as a byte span plus type name withModel::insert_lazy;Model::getdecodes an entity on first access and keeps it, so references stay stable and every later access is a lookup. Type queries, ids,len,containsandnext_idneed no decoding.Model::decode_all(threads)decodes the rest in parallel for a consumer about to touch everything;Model::decoded_lenreports progress. Editing, cloning and removal decode first and then behave exactly as before; clones share the source. Codec::read_owned(Vec<u8>): read from a buffer the model may keep. Defaults toread_bytes;read_fromnow hands its buffer over.
Fixed
Guid::parserejects a GlobalId whose leading character is not0–3(#62). 22 base-64 digits carry 132 bits and a UUID has 128, so a higher leading digit names no UUID. Before, it was accepted andto_uuiddropped the extra high bits:0000000000000000000000and4000000000000000000000expanded to the same UUID, and$$$$…came back from a round trip as3$$$…. Every acceptedGuidnow round-trips throughto_uuid/from_uuidunchanged.- Authoring helpers that validate through
Guid::parse(inifc-spatial,ifc-systems,ifc-structuraland others) now refuse such ids as well.ifc-resourcealready did. A file that was written with one would have failed its own GlobalId check.
ifc-schema
Maintenance release from maint/ifc-schema-0.2: the 0.2.2 code with only the data change below, so it keeps openbim-step 0.5.1 and every dependent's ^0.2.2 resolves to it. main carries the same change.
Changed
- The bundled IFC2X3 TC1, IFC4 ADD2 TC1 and IFC4X3 ADD2 schemas no longer carry WHERE-rule expressions:
EntityDef::where_ruleskeeps each rule'slabel, and itsexpressionis empty. The expressions are CC BY-ND schema text that nothing here evaluates. A schema parsed at run time withSchema::from_expresskeeps them. The artifacts are about 23-38% smaller.data/NOTICE.mdnow states the provenance of the bundled files.
ifc-spatial
Added
SpatialTree::referenced_elements(container)andSpatialTree::referencing_structures(element)(#121): the elements anIfcRelReferencedInSpatialStructurereferences in a container, and the containers referencing an element, in file order and each once, in IFC2X3, IFC4 and IFC4X3. They are kept apart from containment:elements_ofandcontainer_ofare unchanged, and a referenced element is never a second home or aContainedTwiceanomaly. A reference naming an absent entity is reported bydangling().SpatialAnomaly::ContainedInNonContainerandSpatialAnomaly::ReferencedInNonContainer(#121): anIfcRelContainedInSpatialStructureorIfcRelReferencedInSpatialStructurewhoseRelatingStructureis not a spatial container of the release is reported with the relationship and the structure, where containment used to drop it silently.SpatialKind::classify_in(type_name, release),SpatialTree::release()and a re-export ofifc_schema::SchemaVersion.
Changed
- Spatial containers are classified from the release the file's
FILE_SCHEMAdeclares (#121): an entity is a container when that release's bundled table makes it anIfcSpatialElement(IFC2X3:IfcSpatialStructureElement), or it is theIfcProject.ifc-schemais therefore a normal dependency. The IFC4X3 facilities and facility parts (IfcFacility,IfcBridge,IfcRoad,IfcRailway,IfcMarineFacility,IfcBridgePart,IfcRoadPart,IfcRailwayPart,IfcMarinePart,IfcFacilityPartCommon) andIfcExternalSpatialElementwere classified as elements by the old name patterns, so containment into them was dropped; they are nowOtherContainer, as that variant's documentation promised.SpatialKindgains no variant, so this stays additive. A file with no single bundled release is classified as any bundled release would, andrelease()returnsNone. SpatialKind::classifyanswers from the bundled tables instead of name patterns: a name no release declares as a spatial element (such as a vendorIFCSPATIALFOO) is anElement.
Fixed
- Only
IfcRelAggregatesandIfcRelContainedInSpatialStructurebuild the tree (#121). Another relationship family whose relating end is a container placed its targets as contained elements: anIfcRelDeclaresput the project's declared types into the project, and anIfcRelCoversSpacesput a space's coverings into the space. Their absent targets are still reported bydangling().
ifc-systems
Added
- A release-bound
*_with_owner_historyvariant of every systems writer (#202):create_system_with_owner_history,create_port_with_owner_history,assign_to_group_with_owner_history,nest_ports_with_owner_history,connect_port_to_element_with_owner_history,connect_ports_with_owner_history,contain_in_spatial_structure_with_owner_history,reference_in_spatial_structure_with_owner_history,create_group_with_owner_history,authoring::create_distribution_element_with_owner_history,authoring::create_zone_with_owner_history,authoring::create_spatial_zone_with_owner_historyandcreate_classified_system_with_owner_history. Each takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and be anIfcOwnerHistory; none is ever invented. Each binds the model's declared release (a header withoutFILE_SCHEMAbinds IFC4, as the other crates do), runs the plain writer's checks, and lays the record out by attribute name from that release's table. So an IFC2X3IfcDistributionPorthas eight attributes and an IFC2X3IfcZonefive. Enumeration tokens are checked against the release's own table (IFC4 refuses the IFC4X3IfcSpatialZoneTypeEnumtokens). In IFC4 and IFC4X3 a variant writes the plain writer's record with the reference in the optional slot.create_classified_system_with_owner_historybinds the model's release instead of taking a schema argument. SystemAuthoringError::MultipleSchemas,UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringValueType,AuthoringRequired,MissingReferenceandWrongReferenceType, for the variants' refusals: a header binding no single known release, an entity or attribute the release does not declare (IFC2X3IfcSpatialZone,IfcZone.LongName;IfcBuiltSystemoutside IFC4X3), a value it cannot hold, a required attribute left unset, and a missing or wrong-type owner history.SystemAuthoringErroris#[non_exhaustive]and the variants are appended, so this is not breaking. Nothing is staged on a refusal.
Documented
- The plain writers take no model, so they cannot see the release: they still write the IFC4/IFC4X3 layout with
OwnerHistory$, unchanged, and are documented as IFC4/IFC4X3 only, pointing to their variant. IFC2X3 declares none of the classified-system entities, socreate_classified_systemnever wrote an IFC2X3 record.
[0.2.2] - 2026-09-28
ifc-author
Added
AuthorError::ValueForm: a value of the declared type written in the form ISO 10303-21 does not use for it (#199). A typed parameter (IFCAREAMEASURE(12.5)) is refused where the declared type is not a SELECT, and a bare value where it is one, for scalars and for each member of an aggregate, inEntityBuilderandEntityEditor.
Changed
EntityEditorre-checks every slot of the projected entity, as before, so editing an entity whose untouched slots already hold a value in the wrong form is now refused withValueFormuntil that slot is rewritten.
Fixed
- A typed wrapper was judged against its own type only, so any wrapper passed in a slot whose declared type is not a SELECT (#199).
IFCLABEL('x')inIfcQuantityArea.AreaValue, or in an entity-typed slot, is now aTypeMismatch, and so is a wrapper naming a type outside a SELECT's select-list, nested SELECTs included. A declared type the tables cannot resolve still accepts either form.
ifc-classification
Changed (breaking behaviour)
The public API only grows (cargo semver-checks against 0.2.1 reports no break), but the same call now answers differently in the cases below, so the next release is 0.3.0. Each change is required by #194: an IFC4X3 model must be read against its own table, and a header that binds no single known release must be refused rather than read as IFC4.
- Views and authoring bind the release the header declares (#194), as
ifc-materialdoes since #77. OneIFC2X3,IFC4, orIFC4X3/IFC4X3_ADD2declaration binds that release's bundled table (IFC2X3 TC1, IFC4 ADD2 TC1, IFC4X3 ADD2). Before, every header other thanIFC2X3was read against IFC4, so a consumer had to refuse an IFC4X3 model's classifications.classification_schemaanswersSchemaVersion::Ifc4x3for an IFC4X3 header, where it answeredIfc4.- An element that exists only in IFC4X3 (for example
IfcRoad) is anIfcDefinitionSelectmember there, so its classification resolves; read against IFC4 it was aReferenceTypeerror. IfcResourceObjectSelectis the IFC4X3 one, which addsIfcShapeAspect, forexternal_reference_relationship(s),external_references_forandcreate_external_reference_relationship.- IFC4X3 renamed
IfcClassification.LocationtoSpecification, same position and type.ClassificationSystem::location()andClassificationDraft::locationread and write it. No other attribute this crate touches differs between IFC4 and IFC4X3 (IfcClassificationReference,IfcRelAssociatesClassification,IfcExternalReference.Identification, documents, libraries andIfcRootare unchanged).
- A header declaring several schemas fails every read and write with
MultipleSchemas; before, only a set including IFC2X3 did, and e.g.IFC4+IFC4X3was read as IFC4. One declaration without a bundled table (such asIFC4X1) fails with the newUnsupportedSchema; before, it was read as IFC4. A header with no declaration (an in-memory model) still binds IFC4, as in 0.2.0. - Authoring lays each record out by attribute name in the declared release's table instead of writing the IFC4 layout into every model. IFC2X3 gets its own records (a four-attribute
IfcClassification,ItemReference/DocumentIdforIdentification, a three-attributeIfcDocumentInformationRelationship, ...). What IFC2X3 cannot hold is refused with a typed error and nothing is staged:- a draft value for an attribute it lacks (
IfcClassificationDescription,Location,ReferenceTokens; a reference'sDescriptionorSort; a document'sLocation; a document reference'sDescriptionorReferencedDocument; a library'sLocationorDescription; a library reference'sDescription,LanguageorReferencedLibrary) withAuthoringNotInSchema; - text for an attribute it types as a record (
EditionDate,CreationTime,LastRevisionTime,ElectronicFormat,ValidFrom,ValidUntil,VersionDate) withAuthoringValueType; - a required attribute left unset (
IfcClassification.SourceandEdition, andIfcRoot.OwnerHistory) withAuthoringRequired. Soassociate_classification,associate_documentandassociate_libraryrefuse an IFC2X3 model; use their new*_with_owner_historyvariants there; - a reference its selects do not admit (an
IfcClassificationasRelatingClassification, which IFC2X3 types asIfcClassificationNotationSelect; a reference asReferencedSource; a person as libraryPublisher) withAuthoringReferenceType; create_external_reference_relationshipwithEntityNotInSchema. IFC4 output is unchanged, and IFC4X3 output is the IFC4 record at the same positions.
- a draft value for an attribute it lacks (
- Every reference an authoring call checks is checked against the type the release declares, and the error's
expectedlabel is that type's name (the same strings as before for IFC4).
Added
create_classification_in(tx, model, draft):create_classificationin the model's release.create_classificationtakes no model, so it keeps writing the IFC4 layout (valid for IFC4 and IFC4X3) and is documented as such.associate_classification_with_owner_history,associate_document_with_owner_historyandassociate_library_with_owner_history: take a caller-suppliedIfcOwnerHistory, which IFC2X3 requires. It must be in the model or staged, and be anIfcOwnerHistory; one is never invented. This followsifc-material(#77) andifc-properties(#191).ClassificationError::UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringRequiredandAuthoringValueType.ClassificationErroris#[non_exhaustive], so these are additive.
Fixed
DocumentInformation::status()andcreate_documentacceptFINALDRAFT. Every release declares it inIfcDocumentStatusEnum, but both hard-coded a list without it, so a valid document wasInvalidValue. Both enumerations now come from the release's table.effective_classificationsreadsIfcRelDefinesByTypeby attribute name instead of fixed positions.
Verified
- Every attribute, select and enumeration the crate touches is pinned against the three bundled tables, and the tables against the normative EXPRESS under
references/ifc-spec(tests/release_layout.rs). - Records authored in IFC2X3, IFC4 and IFC4X3 through every writer are written as STEP, read back, re-read through the views, and validated by
ifc-validatewith no error finding (openbim-ifc/tests/release_bound_classification_authoring.rs).
ifc-control
Added
create_control_with_owner_historyandassign_to_control_with_owner_history(#198, #202). Each binds the model's declared release (a header withoutFILE_SCHEMAbinds IFC4) and takes a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. It must be in the model or staged on the transaction (UnknownEntityotherwise) and be anIfcOwnerHistory(AuthoringInvalid). None is ever invented. In IFC4 and IFC4X3 the record is the plain writer's with the reference in the optional slot.ControlError::MultipleSchemas,UnsupportedSchema,AuthoringNotInSchema,AuthoringValueTypeandAuthoringRequired, appended to the#[non_exhaustive]enum, so not breaking.
Fixed
create_controlno longer panics on an IFC2X3IfcPermit,IfcActionRequestorIfcPerformanceHistory(#198). It indexed IFC4 positions into their six-attribute IFC2X3 records.create_controlandassign_to_controlnow lay records out by attribute name from the table they are given, so a value that table does not declare is refused (AuthoringNotInSchema: an IFC2X3 permit has noPredefinedType,StatusorLongDescription), one it cannot hold is refused (AuthoringValueType), and a required one left unset is refused (AuthoringRequired). In IFC2X3,identificationis written as the entity's own identifier (PermitID,RequestID,ID), which the IFC4 documentation records as renamed toIdentification. Behaviour change for IFC2X3 callers:IfcRoot.OwnerHistoryis mandatory there, so both plain writers refuse an IFC2X3 schema withAuthoringRequired { attribute: "OwnerHistory", .. }and stage nothing (an IFC2X3 project order used to be written with$); use the*_with_owner_historyvariants. IFC4 and IFC4X3 records are unchanged, record for record.
ifc-cost
Added
CostAuthoringError::MultipleSchemas,UnsupportedSchema,EntityNotInSchemaandAuthoringNotInSchemafor release-bound quantity authoring (#190), and a re-export ofSchemaVersion, which they name.CostAuthoringErroris#[non_exhaustive], so this is not breaking. The crate now depends onifc-schemafor the bundled release tables.
Fixed
mutation::create_quantitywrites the quantity value bare (#190). It wroteIFCQUANTITYAREA('Q',$,$,IFCAREAMEASURE(12.5),$). But<Kind>Valueis declared with a defined measure type, not a SELECT, in IFC2X3, IFC4 and IFC4X3, and ISO 10303-21 writes a typed parameter only in a SELECT slot. It now writes12.5, and a count as the integer4.CostQuantity::valuereads both forms.mutation::create_quantitybinds the model's declared release, with the same rule and output asifc-properties'create_quantity(#190). It wrote the IFC4 five-attribute layout into every model. Now attributes are placed by name from the release's table, so an IFC2X3 quantity has four. The signature is unchanged; the refusals are new:- a
formulain IFC2X3 (AuthoringNotInSchema); QuantityKind::Numberoutside IFC4X3 (EntityNotInSchema);- a header declaring several schemas (
MultipleSchemas); - a header declaring one without a bundled table (
UnsupportedSchema).
A model without
FILE_SCHEMAbinds IFC4 as before. Behaviour change: such a model now refusesQuantityKind::Number, which IFC4 does not declare; declare IFC4X3 to author one. IFC4 and IFC4X3 records are otherwise unchanged apart from the bare value, and matchifc-propertiesbyte for byte in all three releases.- a
ifc-model
Fixed
- Builds for
wasm32-unknown-unknown(#34).ahash's defaultruntime-rngpulled in getrandom 0.3, which fails on that target, so no crate depending on this one could be compiled to WebAssembly. Native builds keep runtime-seeded hashing; wasm32 builds use a compile-time seed.
ifc-schema
Fixed
- Builds for
wasm32-unknown-unknown(#34).ahash's defaultruntime-rngpulled in getrandom 0.3, which fails on that target, so no crate depending on this one could be compiled to WebAssembly. Native builds keep runtime-seeded hashing; wasm32 builds use a compile-time seed.
ifc-spatial
Added
SpaceBoundary::connection_geometry(&Model)andConnectionGeometryAnomaly(#156). The accessor returns theConnectionGeometryreference (slot 6) ofIfcRelSpaceBoundary,IfcRelSpaceBoundary1stLevelandIfcRelSpaceBoundary2ndLevel, in IFC2x3, IFC4 and IFC4X3. Its coordinates are in the relating space's object placement.$or a missing slot isOk(None). A dangling reference, a reference to something that is not a concreteIfcConnectionGeometrysubtype, a value that is not a reference, and a boundary absent from the model are each anErrnaming the boundary and, where there is one, the target. The accessor is a method rather than a new field so thatSpaceBoundary, which has only public fields, keeps its struct-literal construction and this change stays additive.
ifc-systems
Fixed
- The zone readers bind an IFC4X3 header to the bundled IFC4X3 ADD2 table (#194). Before,
zones()read IFC4X3 against IFC4 andlong_name_ofrefused it withUnsupportedSchema. Now:zones()reads IFC4X3 against its own table, and reads every slot by attribute name (IfcZone.Name/LongName,IfcRelAssignsToGroup.RelatedObjects/RelatingGroup) instead of fixed positions. IFC4X3 retypesIfcRelAssigns.RelatedObjectsTypeasIfcStrippedOptionalat the same position, addsIfcBuiltSystemunderIfcSystem(no WR1 zone member), and leavesIfcZoneas in IFC4, so IFC4-shaped data gives the same zones as before;long_name_ofanswers for an IFC4X3 model instead of refusing it.
Added
try_zones(model):zones()with an error channel. A header declaring several schemas is refused withSchemaResolutionError::MultipleSchemasand one without a bundled table withUnsupportedSchema, wherezones()reads both against IFC4. A header with no schema (an in-memory model) reads against IFC4, aszones()does.
Unchanged
zones()keeps its signature and its fallback: a header it cannot bind (none, several, or unknown) reads against IFC4, as in 0.2.0.schema_of,systems,ports,ElementRole::ofandConnectionGraph::buildstill treat IFC4X3 as unverified:schema_ofrefuses it and the bulk readers read it against IFC4. Only the zone readers are verified for IFC4X3.- The
Displaytext ofSchemaResolutionError::UnsupportedSchemano longer claims only IFC2X3 and IFC4 resolve, since the zone readers resolve IFC4X3.
[0.2.1] - 2026-09-28
ifc-approval
Added
associate_approval_with_owner_history(#202). It takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and must be anIfcOwnerHistory: a missing one is refused withUnknownEntity, another entity withAuthoringReferenceType. None is ever invented. In IFC4 and IFC4X3 the reference fills the optional slot. This followsifc-material'sassociate_material_with_owner_history(#77) andifc-properties'*_with_owner_historywriters (#191).ApprovalError::MultipleSchemas,ApprovalError::UnsupportedSchemaandApprovalError::AuthoringRequired { entity, attribute, schema }(#202).ApprovalErroris#[non_exhaustive], so this is not breaking.
Fixed
associate_approvalno longer writesIfcRelAssociatesApprovalwithOwnerHistory$into an IFC2X3 model, where it is mandatory (#202). It binds the model's declared release (a header withoutFILE_SCHEMAbinds IFC4), lays the record out by attribute name from that release's table, and checksRelatedObjectsagainst it:IfcDefinitionSelectin IFC4 and IFC4X3,IfcRootrestricted by WR21 (object and property definitions) in IFC2X3. IFC4 and IFC4X3 output is unchanged. Behaviour change: an IFC2X3 model is refused withAuthoringRequired { attribute: "OwnerHistory", .. }and nothing is staged; useassociate_approval_with_owner_historythere. A header that declares several schemas, or one without a bundled table, is refused withMultipleSchemasorUnsupportedSchemawhere it was written before. The other writers of this crate carry noIfcRootand are unchanged.
ifc-author
Fixed
- An attribute declared as a defined type that aliases an aggregate is an aggregate (#17).
IfcSite.RefLatitude/RefLongitude(IfcCompoundPlaneAngleMeasure = LIST [3:4] OF INTEGER) were refused withAggregateMismatch, which blocked georeferencing. Their elements are now checked against the alias's element type. - A slot that the entity or a supertype redeclares as
DERIVEis written*automatically (#18).IfcSIUnit.Dimensionsand the four derived slots ofIfcGeometricRepresentationSubContextreportedMissingRequired, so no unit assignment or Body/Axis subcontext could be authored. PassingValue::Derivedexplicitly is also accepted.
Added
AuthorError::DerivedAttributerefuses a value or$in a derived slot.AuthorError::NotDerivedrefuses*in a slot the schema does not derive. Before,*was accepted in any slot and the file was invalid. Both apply toEntityBuilderandEntityEditor.
ifc-classification
Added
- Views read IFC2X3 models against the IFC2X3 schema (#51). A header declaring exactly
IFC2X3binds the IFC2X3 table; every slot position, select, and domain comes from it. Before, every model was read with IFC4 positions, so IFC2X3 files only worked where the two releases happen to share a slot.IfcRelAssociatesClassification.RelatingClassificationaccepts IFC2X3IfcClassificationNotationSelect(notation or reference), andRelatedObjectsis checked against IFC2X3IfcRoot.- IFC2X3
ItemReferenceandDocumentIdare read by the IFC4-namedidentification()accessors, since they keep position and meaning. IfcClassificationReference.ReferencedSourcefollows the declared release:IfcClassificationonly in IFC2X3.
classification_schema(model)reports the release reads bind to.SchemaVersionis re-exported.ClassificationNotation,ClassificationView::notations(),notation(), andnotation_values(): an IFC2X3 notation's code is its facets'NotationValues, in authored order, with no invented separator.ClassificationError::NotInSchema: an accessor for an attribute the release lacks (for exampledescription()on an IFC2X3IfcClassification, or anyIfcExternalReferenceRelationshipread in an IFC2X3 model) fails with this instead of returningOk(None), which would read as "authored as empty".ClassificationError::StructuredValue: a text accessor that meets an attribute the release types as a record, such as an IFC2X3IfcCalendarDateEditionDate, returns the record id instead of rejecting a valid value asInvalidValue.ClassificationError::MultipleSchemas: a header declaring several schemas including IFC2X3 cannot be bound to one release.
Unchanged
- IFC4 and undeclared (in-memory) models read exactly as in 0.2.0: every existing IFC4 test passes untouched. On two real IFC4 models, all 1,482 classified objects resolve.
- IFC4X3 headers still read against IFC4, as in 0.2.0. Their own table is out of scope here.
- Authoring stays IFC4-only.
- Projections built with
try_newhave no model header, so they keep reading against IFC4.
Verified
- On five real IFC2X3 models (two ArchiCAD, Solibri, a structural model, Revit), all 3,672 classified objects resolve their effective classifications, and every classification system (5) and reference (62) reads, IFC2X3 calendar edition dates included.
ifc-constraint
Added
associate_constraint_with_owner_history(#202). It takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and must be anIfcOwnerHistory: a missing one is refused withUnknownEntity, another entity withAuthoringReferenceType. None is ever invented. In IFC4 and IFC4X3 the reference fills the optional slot. This followsifc-material(#77) andifc-properties(#191).ConstraintError::MultipleSchemas,ConstraintError::UnsupportedSchemaandConstraintError::AuthoringRequired { entity, attribute, schema }(#202).ConstraintErroris#[non_exhaustive], so this is not breaking.
Fixed
associate_constraintno longer writesIfcRelAssociatesConstraintwithOwnerHistory$into an IFC2X3 model, where it is mandatory (#202). It binds the model's declared release (a header withoutFILE_SCHEMAbinds IFC4), lays the record out by attribute name from that release's table, and checksRelatedObjectsagainst it (IfcDefinitionSelectin IFC4 and IFC4X3,IfcRootrestricted by WR21 in IFC2X3). IFC4 and IFC4X3 output is unchanged. Behaviour change: an IFC2X3 model is refused withAuthoringRequired { attribute: "OwnerHistory", .. }and nothing is staged; useassociate_constraint_with_owner_historythere. IFC2X3 also requiresIntent, which IFC4 made optional, so an IFC2X3 association without one is refused withAuthoringRequiredonIntent. A header that declares several schemas, or one without a bundled table, is refused withMultipleSchemasorUnsupportedSchema. The other writers carry noIfcRootand are unchanged.
ifc-control
Added
assign_to_controlandControlAssignmentDraftstage anIfcRelAssignsToControlwhose relating control is a permit, project order, action request or performance history. Empty, duplicated and self-referencingRelatedObjects, members that are notIfcObjectDefinitions, and missing references are refused;RelatedObjectsTypeis left unset (#99).ControlError::ForeignControlrefuses a relating control another crate owns (cost schedules, cost items, work controls).ControlKind::ALLlists the four owned controls.
ifc-cost
Added
nesting_anomalies(model)andCostAnomaly::NestedTwice { item, kept, rejected, relation }(#57). A cost item that twoIfcRelNestsplace under different parents is reported;NestsisSET [0:1].
Fixed
- A cost item nested under two parents is no longer counted twice (#57).
parent_ofalready returned the first parent, butchildren_oflisted the item under both. Sodescendants_ofandrolled_up_totalincluded it under each parent, and summing overroots()double-counted its value. Now only the kept parent (firstIfcRelNestsin file order) lists it, and a child listed twice under one parent is listed once. Output changes only for files that violate the schema.
ifc-element-type
Added
create_type_inandcreate_supertype_in(#202):create_typeandcreate_supertypein the model's declared release. They bind it (a header withoutFILE_SCHEMAbinds IFC4), lay the record out by attribute name from its table and checkpredefined_typeagainst its enumeration. A type the release does not declare, or declares abstract, is refused withEntityNotInSchema(IFC2X3 has noIfcDoorTypeand no resource or process types; IFC4 has noIfcBearingTypeorIfcBuiltElementType). A token where the release declares noPredefinedTypeis refused withAuthoringNotInSchema.OwnerHistoryis left$, which IFC2X3 forbids, so an IFC2X3 model is refused withAuthoringRequired.create_type_with_owner_historyandcreate_supertype_with_owner_history(#202): the same, with a caller-suppliedIfcOwnerHistory, which IFC2X3 requires. It must be in the model or staged on the transaction (MissingEntityotherwise) and be anIfcOwnerHistory(InvalidonOwnerHistoryotherwise). None is ever invented. This followsifc-material(#77) andifc-properties(#191).ElementTypeError::MultipleSchemas,UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringRequiredandMissingEntity(#202), appended.ElementTypeErroris#[non_exhaustive], so this is not breaking; itsDisplaycovers them.ifc-schemais now a dependency (it was a dev-dependency).
Changed
create_typeandcreate_supertypeare unchanged: they take no model and still write the catalogue's IFC4X3 layout withOwnerHistory$. That is never valid IFC2X3, and they cannot refuse a model that declares another release; their documentation now says so. They also still write$into the four required attributesTypeDrafthas no field for (IfcDoorType.OperationType,IfcWindowType.PartitioningType,IfcEventType.EventTriggerType,IfcFurnitureType.AssemblyPlace), where the model-bound writers refuse withAuthoringRequired.ElementTypeErrormoved to its own module; the public pathifc_element_type::ElementTypeErroris unchanged.
ifc-model
Changed
Model::ids_of_typedocs now point toifc::ids_of_type_including_subtypesfor subtype-inclusive queries, instead of wrongly sayingifc-schemaprovides them.
ifc-occurrence
Added
create_with_owner_history(#202). It takes a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and must be anIfcOwnerHistory: a missing one is refused withOccurrenceError::UnresolvedOwnerHistory, another entity withOccurrenceError::NotAnOwnerHistory. None is ever invented. This followsifc-material(#77) andifc-properties(#191).OccurrenceError::MultipleSchemas,UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringRequired,UnresolvedOwnerHistoryandNotAnOwnerHistory(#202), appended.OccurrenceErroris#[non_exhaustive], so this is not breaking.ifc-schemais now a dependency: the declared release's table lays out each record.
Fixed
createwrites the model's declared release instead of the IFC4X3 catalogue row's layout (#202). It wroteOwnerHistory$, mandatory in IFC2X3, and the IFC4X3 arity,PredefinedTypeslot and tokens into every model. It now binds the release (a header withoutFILE_SCHEMAbinds IFC4), lays the record out by attribute name from that release's table, and checkspredefined_typeagainst that release's enumeration. IFC4X3 output is unchanged for every catalogue row, and IFC4 output for every row IFC4 declares with the same layout (both are tested). Behaviour changes: an IFC2X3 model is refused withAuthoringRequired { attribute: "OwnerHistory", .. }; usecreate_with_owner_historythere. A class the bound release does not declare is refused withEntityNotInSchema: IFC4X3-only classes such asIfcBoreholeare refused in an IFC4 model and in a model whose header declares no schema, which binds IFC4; declareIFC4X3(orIFC4X3_ADD2) to author them. A token only IFC4X3 declares is refused in IFC4 withUnknownPredefinedType, and IFC2X3 classes without aPredefinedType(IfcDoor,IfcWall) refuse one withNoPredefinedType. A required attribute the draft cannot carry is refused withAuthoringRequired, not written$: in IFC2X3,IfcRamp/IfcRoof/IfcStair.ShapeType,IfcReinforcingBar/IfcTendon.NominalDiameterandIfcReinforcingMesh.LongitudinalBarNominalDiameter. Several or unknownFILE_SCHEMAtokens are refused withMultipleSchemasorUnsupportedSchema. The type pairing is still the IFC4X3CorrectTypeAssignedclass in every release, so an IFC2X3IfcDoorStyleis not accepted astyped_by.
ifc-properties
Added
exact_propertyresolves IFC2X3 models (#48). It binds to the release the singleFILE_SCHEMAtoken declares, IFC2X3 TC1 or IFC4 ADD2 TC1, and reads every structural fact from that release's bundled table: slot counts, entity domains (IFC2X3RelatedObjectsisIfcObject, IFC4's isIfcObjectDefinition), type objects (IFC2X3IfcDoorStyleandIfcWindowStylecarry inherited properties like any other type), and theIfcValue/IfcUnitselects. An 8-slot IFC2X3IfcWallresolves under an IFC2X3 header and is a slot mismatch under IFC4, and the reverse.exact_schema(model)reports the release a resolution binds to, so a consumer can bind vocabulary per release without re-parsing the header. It fails exactly whereexact_propertyfails at model level.SchemaVersionis re-exported.ExactPropertyError::NotInSchemanames a construct the declared release does not define, such as anIfcDoorType, anIfcPropertySetDefinitionSet, or anIFCBINARYvalue in an IFC2X3 file. A file that mixes releases fails closed instead of being read with the wrong table.ExactPropertyError::UnsupportedRelationship: a proper subtype ofIfcRelDefinesByPropertiesorIfcRelDefinesByTypethat relates the queried object, such as IFC2X3IfcRelOverridesProperties, is refused. Before, it was skipped as if absent, and the overridden value was returned.
Changed
- IFC4X3 headers stay
UnsupportedSchema: the table is bundled, but exact semantics for it are not verified yet (#33 tracks IFC4X1/IFC4X2). - On five real IFC2X3 models, every sampled lookup failed with
UnsupportedSchemabefore. Now 715 of 726 sampled lookups resolve; the other 11 areDuplicateMatchingSets, where a property asked for by name alone is carried by two sets assigned to the same object. The same refusal occurs on the IFC4 control models.
ifc-schedule
Added
create_task_with_owner_history,create_sequence_with_owner_history,create_work_control_with_owner_history,assign_tasks_to_control_with_owner_history,nest_tasks_with_owner_history,create_work_calendar_with_owner_history,create_event_with_owner_historyandcreate_procedure_with_owner_history(#202). Each takes the model and a caller-suppliedIfcOwnerHistoryid, which IFC2X3 requires on everyIfcRoot. The id must be in the model or staged on the transaction and must be anIfcOwnerHistory; a missing one is refused withMissingReference, another entity withWrongReferenceType. None is ever invented. Each binds the model's declared release (a header withoutFILE_SCHEMAbinds IFC4; several or an unknown token are refused) and lays the record out by attribute name from that release's table. In IFC4 and IFC4X3 the record is the plain writer's with the reference in the optional slot. This followsifc-material(#77),ifc-properties(#191) andifc-control(#198).- In IFC2X3 the variants write
IdentificationasIfcTask.TaskIdandIfcProcedure.ProcedureID, andPredefinedTypeasIfcProcedure.ProcedureType(renames stated in the IFC4 ADD2 TC1 documentation). What IFC2X3 cannot hold is refused, never dropped:IfcEventandIfcWorkCalendar(EntityNotInSchema); a task'sLongDescription,TaskTimeorPredefinedType(AuthoringNotInSchema); a sequence'sIfcLagTimereference where IFC2X3 declares anIfcTimeMeasure, and a work control's ISO 8601 dates where it declaresIfcDateTimeSelect(AuthoringValueType); a missingTaskId,TimeLagorProcedureType(AuthoringRequired); and aUSERDEFINEDprocedure, whose IFC2X3 WR4 needs aUserDefinedProcedureTypethe draft cannot carry (InvalidValue). Enumeration tokens are checked against the bound release, so an IFC4X3-onlyIfcTaskTypeEnumtoken is refused in IFC4. ScheduleAuthoringError::MultipleSchemas,UnsupportedSchema,EntityNotInSchema,AuthoringNotInSchema,AuthoringValueType,AuthoringRequired,MissingReferenceandWrongReferenceType, appended afterInvalidValue. The enum is#[non_exhaustive], so this is not breaking.SchemaVersionis re-exported, which they name. The crate now depends onifc-schemafor the bundled release tables.
Changed
- The plain
IfcRootwriters (create_task,create_sequence,create_work_control,assign_tasks_to_control,nest_tasks,create_work_calendar,create_event,create_procedure) take no model and so cannot see the declared release. Their signatures and output are unchanged: the layout IFC4 and IFC4X3 share, withOwnerHistorywritten$. They are now documented as IFC4/IFC4X3 only; in IFC2X3, where that record is invalid, use the*_with_owner_historyvariants (#202).create_recurrence_patternmoved to its own module; its path and behaviour are unchanged.
Fixed
create_lag_timewritesIfcLagTime.LagValueas the member ofIfcTimeOrRatioSelectit is (#201): a string asIFCDURATION('P5D')and a number asIFCRATIOMEASURE(0.5), an integer as that REAL. A bare value in a SELECT slot does not say which member it is. A value already typed asIFCDURATIONorIFCRATIOMEASUREis accepted; any other is refused, as before.sequencesreads both forms, as before.
ifc-schema
Added
Schema::subtypesandSchema::direct_subtypes: every entity inheriting from a name, the inverse ofis_a. Checked againstis_afor every ordered entity pair of all three bundled schemas (#32).
Changed
- Requires
openbim-step0.5.1, which provides the downward walk.
ifc-spatial
Added
SpatialTree::anomalies()andSpatialAnomaly(#54). An element placed by twoIfcRelContainedInSpatialStructures is reported asContainedTwice, and a container aggregated by two parents asAggregatedTwice. Each names the element or child, the kept and rejected parent, and the rejected relationship. The first relationship applied still wins. Restating the same parent is not reported.
Changed
- For invalid files only: the rejected container no longer lists a doubly contained element in
SpatialNode::elements/elements_of. Before, the element appeared in both containers whilecontainer_ofreturned only the first, so the two views disagreed. Valid files are unaffected.
ifc-step
Changed
- Requires
openbim-step0.5.1, matchingifc-schema0.2.1. Both pin the parser exactly, so the pair must move together.
ifc-structural
Added
stage_boundary_condition_in(tx, schema, kind, draft)(#200, #201). It lays the record out byschema's attribute names (IFC2X3 still saysLinearStiffness...) and writes each stiffness in the form its declared type requires there: bare in IFC2X3, whose stiffnesses are plain measures, and the typed parameter of the SELECT member in IFC4 and IFC4X3. A boolean where the release admits none (every IFC2X3 stiffness) is refused withInvalidDraftValue. It takes&Schemalike the crate's other writers.
Fixed
stage_boundary_conditionwrites the IFC4/IFC4X3 form correctly (#200, #201):- an edge condition's translational stiffness is
IFCMODULUSOFLINEARSUBGRADEREACTIONMEASURE(..), the member ofIfcModulusOfTranslationalSubgradeReactionSelect, instead ofIFCMODULUSOFTRANSLATIONALSUBGRADEREACTIONMEASURE, which no release declares; - warping is
IFCWARPINGMOMENTMEASURE(..), the member ofIfcWarpingStiffnessSelect, instead ofIFCROTATIONALSTIFFNESSMEASURE; - a boolean stiffness is
IFCBOOLEAN(.T.)instead of a bare.T., which does not say which SELECT member it is.
It is now
stage_boundary_condition_inwith the bundled IFC4 table, so it is still not correct in IFC2X3; its docs point IFC2X3 callers to the new writer. The readers accept a bare and a typed value, as before.- an edge condition's translational stiffness is
ifc-systems
Fixed
- IFC2X3 models are read against the IFC2X3 schema (#52). Before, every model was read with IFC4 ancestry, which gave wrong answers on IFC2X3:
systems()listedIfcZoneas a system and rejectedIfcElectricalCircuitasNotASystem. In IFC2X3 a zone is a plain group and an electrical circuit is a system; both are now read that way. A relationship assigning members to an IFC2X3 zone is still reported bysystems()asNotASystem, since a zone is not a system there.zones()checks WR1 against the declared release. IFC2X3 admitsIfcZoneandIfcSpacemembers only; it has noIfcSpatialZone.ElementRole::of,ports(), andConnectionGraph::builduse the declared release's ancestry, so an IFC4-only record in an IFC2X3 file (such asIfcPipeSegment) has no role.
Added
schema_of(model)reports the release reads bind to, or why none could be bound (SchemaResolutionError: missing, multiple, or unsupported, including IFC4X3).SchemaVersionis re-exported.long_name_of(model, zone)readsIfcZone.LongNameand returnsSchemaGap::NotInSchemawhen the release has no such attribute (IFC2X3), instead of aNonethat reads as "authored empty".
Unchanged
- IFC4 models, and models with no
FILE_SCHEMA(every in-memory model), read exactly as in 0.2.0. Every existing test passes unmodified. - The bulk readers keep their signatures and have no error channel. A header they cannot bind (none, several, or IFC4X3) reads against IFC4, as in 0.2.0. Call
schema_offirst to refuse such a model. - Authoring stays IFC4-only.
Verified
- On three real IFC2X3 ventilation models (2 systems each), six other IFC2X3/IFC4 models, and an IFC4 MEP model with 16 systems, 11,666 ports, and 5,833 connections, the results are identical before and after. None of the local IFC2X3 files contains a zone, electrical circuit, or port, so the IFC2X3-specific paths are proven by the fixture tests only.
ifc-tabular
Added
- Borrowed read views:
TabularViewreadsIfcTable(rows and columns) andIfcRegularTimeSeries/IfcIrregularTimeSerieswith their value records under a declared IFC4 or IFC4X3 schema, locating slots by name. WR1 (ragged row), WR2 (more than one heading), malformed slots, arity mismatches, empty lists and dangling or mistyped references are reported asTabularIssues instead of being dropped. IFC2x3 is refused withTabularReadError::UnsupportedSchema(#120).
ifc-template-catalog
Fixed
- The built-in environmental advisories cite their decision record at its restored path,
docs/adr/0017-versioned-psd-qto-catalog.md; the old0010path had been reassigned to an unrelated ADR.
ifc-xml
Fixed
- A codec built with
XmlCodec::with_schemanow reads its own output back in slot order. Scalars are written as XML attributes and structured values as child elements, and the reader kept that document order, soIfcLocalPlacement($, #2)came back as(#2, $). Schema attribute names now resolve to slots through the same schema; found by the corpus round trip (#118). - Whitespace in element text is data: a padded string, enum or binary inside a list or typed wrapper no longer loses leading or trailing whitespace (#116).
- A string such as
" i7"no longer reads back as a reference. The writer now decides attribute versus element by running the reader's own inference, so the two cannot disagree (#116). - An attribute literal that overflows to infinity, such as
1e999, reads as text instead of an infinite real, matching the explicitkind="real"path and the writer, which both refuse non-finite reals (#116). kind="logical"text other thantrue,falseorunknownis anInvalidScalarerror instead of silently becoming unknown (#116).- Tab, line feed and carriage return are written as character references, which conforming XML parsers do not normalize away, and a typed wrapper's
typeattribute is escaped (#116).
Added
XmlError::DuplicateSlotandXmlError::MissingSlot: two values for one attribute slot, or a positionala<i>name that skips slots without a schema declaring them, are refused with the entity path instead of shifting later attributes. With a schema, an omitted attribute reads as unset.- A seeded property test of the scalar contract and a differential STEP -> ifcXML -> Model -> STEP round trip over every committed fixture, in positional, schema-named and strict-profile configurations (#116, #118).
[0.2.0] - 2026-10-13
ifc-alignment
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-approval
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-author
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-classification
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-constraint
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-control
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-cost
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-element-type
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-geometry
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-georef
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-material
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-model
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-occurrence
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-properties
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-resource
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-schedule
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-schema
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-spatial
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-step
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-structural
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-style
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-systems
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-tabular
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-template-catalog
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-validate
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
ifc-xml
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
openbim-ifc
First release under per-crate versioning. See the repository changelog for the family-wide history that produced this version.
openbim-ifc-cli
Added (#450)
convertreads and writes ifcZIP: an input archive is recognized by its ZIP signature on every command, and an output ending in.ifczip, or--to ifczip, is written as one Deflate entry named after the output file, STEP unless--inner ifcxml(with--layout) asks for ifcXML. An archive without exactly one model, or whose entry is in another format, is refused asparse; an encrypted entry or another compression method asunsupported; an entry past the decompression budget asbudget-exceeded.inforeports the format asSTEP in ifcZIP (model.ifc).
Fixed
- A native-layout ifcXML document whose attributes are named by position (
a0, as the bindings'write_ifcxmlwrites it) reads, with the declared release's tables resolving both kinds of name; a strict read refused it.
Semver: minor (additive).
Changed (#463)
- An entity named by
GlobalIdis found with the facade'sby_guidinstead of the command's own scan. AGlobalIdseveral entities carry still exits 2 naming every holder (previously the first two), and a 22-character reference outside IFC'sGlobalIdalphabet or range is a usage error rather than a lookup that finds nothing.
Semver: patch.
[0.1.0] - 2026-10-09
openbim-ifc-cli
Added (#329, the first part)
- The
openbim-ifccommand, over theopenbim-ifcfacade only:validate FILE...: each file against the schema its header declares;--format human|json|sarif,--max-findings,--deny-warnings,--include-unsupported. Exit 0 when every file passes, 1 on an error, an evaluation error, a truncated report or (with--deny-warnings) a warning, 2 when a file could not be validated.convert IN OUT: STEP to ifcXML and back, the output format from the extension or--to;--layout native|xsdfor ifcXML output;--input-layout auto|native|xsd;-writes standard output.info FILE: header, declared schema and whether it is bundled, entity and per-type counts;--format human|json.psets FILE ENTITY: the exactly resolved property and quantity sets of an object or type object, by#idorGlobalId, occurrence and inherited;--format table|json|csv.tree FILE: the spatial structure with element counts or (--elements) the elements, and the anomalies the tree found;--format human|json.lint FILE...: products no model viewer will draw (exit 1 on a finding);--format human|json|sarif.
- Typed refusals with a stable kind on stderr (
usage,io,parse,unsupported-schema,unsupported,missing-entity,wrong-entity-type,missing-reference,invalid-model,budget-exceeded,write) and exit code 2. - Prebuilt binaries on each
openbim-ifc-cli-v*GitHub release for Linux (x86_64, aarch64; static musl), macOS (x86_64, arm64) and Windows (x86_64), withSHA256SUMS;install.shand a Homebrew formula generated for the release;cargo binstallmetadata.
Added (#376, #377, #378)
- Debian packages
openbim-ifc_<version>_amd64.deband_arm64.debon each release, packed from the same static binaries byscripts/package.py deb(the binary in/usr/bin, the README and the licence in/usr/share/doc/openbim-ifc/, no dependencies) and listed inSHA256SUMS;scripts/check-deb.shinspects them and installs, runs and removes them withdpkgon Ubuntu 22.04 (x86_64 and aarch64) in CI and in every release build. No apt repository. - A Nix flake at the repository root:
packages.<system>.openbim-ifc(anddefault) built from source with the pinned toolchain andCargo.lock, andapps.<system>.default, sonix run github:openbimrs/ifc -- --versionworks on x86_64 and aarch64 Linux and macOS; checked in.github/workflows/nix.yml. install.ps1for Windows (irm .../install.ps1 | iex): picks the x64 or Arm64 archive, refuses a checksum mismatch, installs to%LOCALAPPDATA%\Programs\openbim-ifc\bin(or-Prefix) and offers to add it to the userPATH; honoursOPENBIM_IFC_BASE_URLlikeinstall.sh.scripts/check-install.ps1tests it on Windows in CI and in every release build, and the release attaches it.- A Windows Arm64 binary (
aarch64-pc-windows-msvc) on each release.
Semver: a new crate, first release 0.1.0.