Skip to content

v0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Nov 01:24
· 50 commits to main since this release
511b56e

Breaking changes

  • Legacy IFC profile: the GlobalId of a type-level IfcRelAssociatesMaterial relation is now seeded from the material element id list (type-materials:{materialIds}, as materials:{materialIds} already is for product-level relations, and type-layer-set:{seed} from the host type id or layer-set group key) instead of the STEP entity number of the relating material, material list or layer set. Type-level material relation GlobalIds in the legacy profile change once and no longer shift when entities are added or removed earlier in the file; product-level material relations and the current IFC profile are unchanged. (Closes #88)

Added

  • Preview project src/RvtIO.Scene: RvtSceneReader turns a document into a scene of the IOScene 0.1 contract, with the element meshes in metres (shared symbols as instanced geometry), the Revit UniqueId as the source id, the IFC GlobalId of Revit's IFC export as the IfcGuid, names, categories, materials and the instance and type parameters as property sets of their display text. See the scene adapter.

  • Package RvtIO.Scene: the scene adapter is now published on the RvtIO release tag, depends on exactly the same RvtIO version and references IOScene with the range [0.1.0, 0.2.0-0.0) (a release that would depend on an IOScene preview, or on a version nuget.org refuses in a dependency, fails).

  • API: ElementMeshes.Extract (namespace RvtIO.Meshes) returns the triangle meshes of the drawable model elements of an opened RvtDocument, in feet, without reading the file again: per element its own triangles (one mesh per material, flat normals, counter-clockwise around them) and, for an element that is exactly its family symbols placed rigidly, the placements of shared symbol meshes in the frame of the family, with a coverage per element (complete, partial, box in place of a rescaled symbol, no geometry, undecoded; partial also when a placed symbol or a symbol nested in one has no geometry, or a Geometry node that Revit exports has no decoded polygon). A cut or clipped instance, an instance with faces of its own and a scaled one keep their own triangles; a mirrored placement keeps the winding of its symbol and carries the mirror in its transform. The drawable elements follow a subset of the product rules of the IFC export of the current profile (listed in the ElementMeshes remarks, with those not applied, such as the "export to IFC" flag and topography, so the meshes can include elements the IFC leaves out); the face materials and the hidden Geometry nodes follow its rules. The types are listed in the public API. IFC and JSON output is unchanged.

  • CLI and diagnostics: --strict exits with code 6 (RvtExitCodes.PartialRead) instead of 0 when a read finishes partial without exceeding a reader limit (a stream member failed to decode, was rebuilt by structural recovery, or decoded without a matching checksum, or Global/ElemTable decoded no row). Nothing is exported when exit code 6 is returned; limits.strict records the option and issues[] lists the affected streams and reasons (failed-members, recovered-members, unverified-members, no-rows) alongside streams[]. Default behavior without --strict is unchanged. (Closes #54)

  • Default output, current IFC profile: every beam with a straight drive curve gets Pset_BeamCommon.Slope, the angle in degrees of its drive curve to the horizontal (0 for a horizontal beam, never negative), whether or not BaseQuantities are exported and whatever its body; its quantities are unchanged. A beam without a straight drive curve gets no Slope.

  • Default output, current IFC profile: Pset_BeamCommon.Span of a beam without a stored length is the length of its straight drive curve; a beam without one has no Span.

  • Default output, current IFC profile: a round column gets the volume and surface of the measured node that carries its two exact end faces in place of the open tessellated side. A round column is a vertical cylinder: exactly two exact planar end faces, horizontal, without holes, of at least eight corners on one circle (the same for both faces), each with an exact area within 0.5% of the area of that circle (a regular polygon of 32 corners or fewer is refused), and every other face with its corners on the cylinder between the end faces. The node is taken only when it is the one node that holds nothing but end faces. It is used for NetVolume after the sources that come first for a column (the Medium solids, the nominal area of a tube, the computed host volume, the measures of all nodes) and for CrossSectionArea (half the surface) of a column without extrusion data. When the two end faces also have the same exact area (a vertical prism) the column gets the extrusion data Length, CrossSectionArea (the area of its circle), OuterSurfaceArea and GrossVolume. Other columns are unchanged. Measured on the round columns of one sample (corner distances to a millionth of the radius, or eight units in the last place of a 32-bit coordinate where that is more).

  • Default output, current IFC profile: Pset_ColumnCommon.Slope (90 degrees for a vertical extrusion) is written for every column with a body of its own, whatever its shape, not only for the first column of its type; the columns that share a mapped body still carry it on the first one, and a column whose body is not recognised keeps the previous rule (the first column of its type). A round column (as above, with end faces of the same exact area) is judged on all its faces, its exact end faces being listed outside the tessellated shell of its side; other columns are judged as before. Compared with the reference only on the round columns of one sample (Slope equal on all 152); the columns of two other samples, which are not round, are unchanged.

  • IFC export: a structural framing element whose structural type is Brace is an IfcMember (and its type an IfcMemberType) in every file, not only in files whose geometry is not decoded; before, a brace of a Revit 2022 file was an IfcBeam. Its Pset_MemberCommon carries LoadBearing (the occurrence parameter, else true), as does that of its type in the current profile, and, in the current profile, a Span that is the exact length of the straight drive curve (the stored length, which the file rounds to under 0.1 mm, only when there is no such curve); a mullion gets neither. A brace with a straight drive curve gets Pset_MemberCommon.Slope, the angle of that curve to the horizontal, as a beam does, in every file and not only where the body is decoded (current profile). In a file whose geometry is decoded the Length and CrossSectionArea of a brace stay those of the beam rule, the profile length and area; its other quantities are NetVolume and NetSurfaceArea. A brace loses Pset_BeamCommon, as it is no longer a beam. In a file whose geometry is not decoded (Revit 2023 and later) the Length and CrossSectionArea of a brace keep the member rule, where the section is half the surface area of the solid. The legacy profile also writes a brace as an IfcMember, with LoadBearing but without Span and without the type-level set. In a Revit 2023 file, where braces already were members, the type-level Pset_MemberCommon is now written for them too (current profile). Measured against the reference on one Revit 2022 sample (all 22 braces: class, type, IsExternal, LoadBearing, Reference and Span equal), three synthetic Revit 2022 models and two Revit 2023 samples (LoadBearing and Span of all 81 braces equal); the Length and CrossSectionArea of the Revit 2022 braces are unchanged from the beam output. The reference writes no Slope for a brace, so this property is a deliberate difference from it. Beams are unchanged.

  • IFC export, current profile: a structural framing element whose structural type is Non-structural is an IfcBuildingElementProxy (and its type an IfcBuildingElementProxyType) instead of an IfcBeam, with its quantities and Pset_BuildingElementProxyCommon. Measured on one Revit 2021 structural file only (18 of 18 such elements agree with the reference), not on a Revit 2023 or later file; the legacy profile is unchanged.

  • IFC export, current profile: ducts, pipes, conduits and cable trays of files with their own class schema (Revit 2021 and 2023 and later) take their length from the straight drive curve of the element, as those of a Revit 2022 file do, so each one carries the Length property of Pset_FlowSegmentDuctSegment and Pset_FlowSegmentPipeSegment (the curve length in millimetres) and the parameters derived from the curve (surface area, elevations, slope) that the reader had left unset in those files. A driver that is an arc, or that holds no straight curve and is not the spline of a flexible duct or pipe, gives no length, as before.

  • IFC export, current profile: flexible ducts and flexible pipes carry the Length property of Pset_FlowSegmentDuctSegment and Pset_FlowSegmentPipeSegment, the arc length in millimetres of the Hermite spline that drives them. A flexible pipe of a Revit 2022 file already had it; a flexible duct of a Revit 2022 file did not, and neither did a flexible duct or pipe of a file with its own class schema (Revit 2021 and 2023 and later), whose spline nodes were not read. Measured against the reference on five files: all 113 flexible ducts of a Revit 2022, a Revit 2023 and a Revit 2024 sample (113 of 113 in each, equal within 1e-6 relative) and the 6 flexible ducts and pipes of a Revit 2021 and of a Revit 2023 sample (6 of 6 in each) now match; nothing regressed or was lost in those samples. A spline whose nodes are incomplete, or a driver that queues more than one spline, gives no length, as before. Files of Revit 2020 and of Revit 2025 and later were not measured.

  • IFC export, current profile: the ducts, pipes, conduits, cable trays and pipe insulation of a Revit 2021 file are named after the English name of their system family, as those of a later file are (Pipe Types:{type}:{id}, Rectangular Duct:{type}:{id}, Cable Tray with Fittings:{type}:{id}, Pipe Insulation:{type}:{id}) instead of after the OST_ category name. The other system families of a Revit 2021 file are the subject of the entries below, and the MEP curve families of a Revit 2020 or earlier file keep their category names.

  • IFC export, current profile: the curtain walls and stairs of a Revit 2020 or 2021 file are named after the English name of their system family, as those of a later file are (Curtain Wall:{type}:{id}, Assembled Stair:{type}:{id}, Stair:{type}:{id} for a stair of the older record layout) instead of after the OST_ category name, so the parts that aggregate to them (mullions, plates, railings, slabs, doors, windows and stair flights) carry the same parent name; the names of some members and slabs change with it. The English name is used whatever language the file was saved in, as for the Revit 2021 MEP curves. A curtain wall of a Russian Revit 2020 file no longer takes the family name of a mullion. Measured on Revit 2020 and 2021 sample files (only Assembled Stair occurs in them; the names of Cast-In-Place Stair and Precast Stair follow the stored stair kind and are covered by unit tests only). The other system families of these releases are the subject of the entries below, the MEP curve families are named for Revit 2021 only, and the legacy profile is unchanged.

  • IFC export, current profile: the floors, foundation slabs and building pads of a Revit 2020 or 2021 file are named after the English name of their system family, as those of a later file are (Floor:{type}:{id}, Foundation Slab:{type}:{id}, Pad:{type}:{id}) instead of after the OST_ category name, so the ObjectType of the slab and the name of its material layer set (Floor : {type}) change with it. The English name is used whatever language the file was saved in, as for the curtain walls and stairs of those releases. A floor whose type is a foundation slab type is a Foundation Slab. Measured on ten Revit 2020 and 2021 sample files (three of Revit 2020, which also hold a foundation slab and two pads): 1638 slab Name and ObjectType slots now agree with the reference where none did (1190 in one structural file, then 236, 96, 42, 22, 22, 10, 10, 8 and 2), none regressed, and the 44 renamed material layer sets of seven of those files match the reference by name. Files of Revit 2022 and later are byte-identical to before, the compact JSON is unchanged, and the legacy profile, which requests no locale, is not changed by construction (not measured). Six slabs of one file keep a type name that differs from the reference; that defect was there before and is not changed.

  • IFC export, current profile: the walls, roofs, ceilings, railings and stair parts of a Revit 2020 or 2021 file are named after the English name of their system family, as those of a later file are (Basic Wall:{type}:{id}, Basic Roof:, Sloped Glazing:, Compound Ceiling:, Railing:, and for stair runs and stringers or carriages in the ObjectType Non-Monolithic Run:, Monolithic Run:, Stringer:, Carriage:) instead of after the OST_ category name. The names of wall openings, of the material layer sets of walls, roofs and ceilings and of the parent roof of curtain panels follow. The English name is used whatever language the file was saved in; the legacy profile, which requests no locale, is not changed. Measured against the reference on ten Revit 2020 and 2021 sample files (three of Revit 2020), compared with the build before this change: 4309 IFC slots now agree with the reference where none did (2335, 1055, 393, 216, 132, 130, 21, 21, 4 and 2 per file), and no slot regressed, was lost or was added. 2313 products were renamed (by GlobalId): 2282 of them have exactly the reference values in every slot that changed, 13 (12 wall openings and one curtain wall) have no reference element under the same GlobalId, and 18 keep a changed slot that still differs from the reference (see below); 142 material layer sets were renamed. Files of Revit 2022 and later (five samples, and a Revit 2021 file without these families) are identical to before apart from the file header timestamp, and the compact JSON is unchanged in the thirteen files where it was compared (seven of the ten files above, that Revit 2021 file and the five later samples; it was not compared for the other three of the ten). The legacy-profile export of four of the ten files (two of Revit 2020, two of Revit 2021) has an identical data section; the other six were not measured in that profile. Not given a family name: wall sweeps and stair landings, which both occur in the samples; a wall sweep already carries the same name as the reference and a stair landing is named after its type, as the reference names it, so neither needs one. Not changed either: the elements whose type the reader does not resolve and which therefore have no type name: 8 strip footings of two files keep the OST_ name (their type link is present in the file and is not read yet; the Wall Foundation name is in the list but affected no element), and 5 stair flights of one file keep it (in the Name and the ObjectType of three of them, in the ObjectType of the other two; why their type is not resolved was not traced). Remaining differences: 11 stair members of one file keep a Name that differs from the reference (the stair they are attributed to), and 5 of those also have a stringer type where the reference has a carriage type; 6 flights of one file whose Name still differs from the reference in the stair they are attributed to (their ObjectType now matches); one covering, which was already different before.

  • Default output, current IFC profile: a column whose base level is a Level that is not a project storey is contained in the highest storey at or below that level, and in the building when no storey is that low, instead of the storey chosen by the bottom of its body. Measured on structural columns of one project; the building container of a column below the lowest storey is not checked against Revit.

  • Default output, current IFC profile: a duct, pipe, cable tray, conduit, fitting, accessory, insulation, terminal, sprinkler, or an electrical, lighting, mechanical, plumbing or food service equipment or fixture whose level is a Level that is not a project storey is contained in the highest storey at or below that level, and in the building when no storey is that low, instead of the storey chosen by the bottom of its body (the same rule as for columns on such a level). A nested element reads that level from its top-level instance, as it already did for a level that is a storey. For a family instance the base level is read before its own level parameters, so a base level that is not a storey decides the container as one that is a storey already did. Measured on three Revit 2021, 2023 and 2025 saves of one building-services project and on two saves of a second one, by the container of the products that both the output and the reference write: 653 of 6404 (2021) and 652 of 6404 (2023, 2025) products were in another container than in the reference, now 8 and 7; 3 of 4929 and 3 of 4898 in the second project, now 0. In two architecture files (1763 and 1804 matched products) the 5 pipes and fittings on such a level now agree, with no other container changed. Files without a non-storey level are unchanged (gate: no regressed or lost slot, no changed output on the other building-services samples). Not covered: a floor or an entity of another category on such a level, and the flow elements whose level is a project storey but whose container still differs.

  • IFC export: a roof that has no body of its own and holds curtain mullions or panels is written as an IfcRoof that aggregates them (IfcRelAggregates: mullions as IfcMember, panels as IfcPlate) in a file whose geometry is not decoded, as it already was in Revit 2022 files; its children are no longer contained in a storey. A roof none of whose children is exported stays an ordinary IfcRoof product, as before. The rule does not look at the roof type and is not tied to a profile. Measured against the previous build: Revit 2023 to 2026 files (arch23, school23, goldenarch23, dach24, racadvanced26) gain 12 or 120 aggregated mullions and 8 or 42 plates and the aggregate and container slots of the roof children now match the reference, with no regressed slot (measured with the current profile; the legacy profile was not measured); a Revit 2021 file gains the same aggregate under the right roof; the parent roof there is named as the reference names it by the entry on the walls, roofs, ceilings, railings and stair parts above (current profile). The Revit 2022, racbasic21 and jparch21 samples are unchanged.

  • IFC export: a curtain panel or mullion that names its curtain wall only by the plain host id (no curtain host id) joins the IfcRelAggregates of that wall, under the same checks as before (curtain wall function, not a basic or stacked wall, no IFC override, single occurrence); a curtain host id, when present, still wins, and doors, windows and walls are still matched by the curtain host id only. In one Revit 2025 sample this adds 60 mullions and 13 panels to the aggregates of their curtain walls, with no regressed slot; the curtain-wall aggregates of one Revit 2021 sample also grow by one mullion and two panels, which was not traced to a rule.

  • IFC export: the bars of area reinforcement (RebarInSystem) of Revit 2020 and 2021 files are written as IfcReinforcingBar with the group of their system, as those of later files already were. Those releases store no bar curves in the layers of the system; the bars are kept as one held instance per RebarInSystem, and the reader now rebuilds the curves of each from its shape, bar type, hooks, parameters and frame, the same way as for a standalone bar. The files of Revit 2022 and later are read as before (the rule is chosen by the schema of the file, not by the version number). Measured on the 2020 and 2021 versions of one sample project: 224 of 224 such bars are written in each (346 of 346 IfcReinforcingBar in total, 122 before), none extra, with the same count, name, bar type, diameter and area as the expected output and the swept disk start points of all 346 within 0.01 mm. The Revit 2022, 2023, 2024, 2026 and 2027 versions of that project give the same bar entities as before. Not measured: other Revit 2020 and 2021 projects, the containment of these bars in other files, systems with an extensible storage entity in front of their data (still skipped).

  • IFC export: the bars of a Rebar of a Revit 2023 or later file whose block stops behind an extensible storage entity are written when their shape can be rebuilt (shape driven bars). The payload of such an entity lies in the stream before the objects queued after it, so the reader stopped with an error and dropped the bar although its RebarInstance, which is read before the entity, was whole. A walk that stops after the entity is now cut to the objects queued before it and the bar is solved from the shape as for a bar without a cache. Measured on the Revit 2024 and 2023 sample projects (same project) and the Revit 2026 one: 107 of the 124 Rebar elements are written (35 before, 39 in the 2026 file), all 107 present in the reference, none extra; the points of the centre lines and the bar length agree with the reference for all 107 (global coordinates, within 0.01 mm; arcs compared by count only). The other 17 Rebar (10 of a shape with a 10 degree S-bend, others not yet read), the 42 RebarInSystem and the 14 bars of 3 RebarContainer of those files are still missing. All 107 bars are contained in a storey, and the storey is the one of the reference for all 107: 56 of them are members of a parts assembly, which the reference writes and this exporter does not (the assembly also holds the generic model parts of the wall, 84 of them in the Revit 2024 file, which stay contained in a storey here), so they are not aggregated by it. A bar of an assembly is contained in the highest storey at or below the bottom of the Part that hosts it, as the reference places the assembly (all 56 agree, the bottom of the bar itself gives the wrong storey for all 56); the rule is measured on this one project only, and a bar of an assembly without a Part box on its host chain is placed by its own bottom like the others. The legacy profile was not measured. Revit 2021, 2022, 2023, 2024 and 2026 structural samples are unchanged (IFC identical).

  • IFC export, current profile: an HVAC zone that has at least one exported space is written as an IfcZone named {number}:{id} (the default zone is Default:{id}; a zone with no name is written as :{id} with an empty number), with the zone number as Pset_ZoneCommon.Reference and an IfcRelAssignsToGroup of its spaces in id order. The group holds every exported space that names the zone; the default flag is read from the zone record. A zone with no exported space (an empty zone, or the default zone when every space belongs to another zone) is not written. The GlobalId is the export id of the zone; a zone without one, or whose export id was already used, gets an id derived from its element id, and an IfcGUID parameter on the zone is not applied. Pset_ZoneCommon.Reference takes its IfcPropertySingleValue from the shared property-value pool, so it can share that entity with other property sets that hold the same value. Measured against the reference on five MEP samples (Revit 2021, 2023, 2024 and 2025): all 51 + 51 + 1 + 1 + 1 zones are present with the same GlobalId, name and group GlobalId, no slot regressed and no zone is extra. In the three samples with a default zone the group differs from the reference by the spaces that the file itself writes differently (1 fewer, 17 fewer and 4 more); in the last one the group is a superset of the reference group (4 extra spaces that this file exports in addition). No sample has a zone without a name, so that case is not compared. The name of the default zone is not checked in a file saved in another language. The legacy profile is unchanged (no IfcZone is written).

  • IFC export, current profile: an electrical circuit whose base equipment (the panel it is wired from) is an exported product is written as an unnamed IfcSystem (empty Name, Description and ObjectType, the IfcGUID of the circuit as GlobalId) with IfcRelServicesBuildings and an IfcRelAssignsToGroup whose only member is the base equipment, as the named duct and pipe systems are; the loads of the circuit are not members. The base equipment is found in the circuit's own connector manager: the connector that holds the reference of type 4 with an index of 50000 or more to an element other than the circuit gives a connector number, and the base equipment is the first reference to another element in the connector that sits at that array position. The number equals the position unless the numbers skip a value; then the element is a load of the circuit, not the panel, and it is the element the reference writes in the checked files. A circuit without such a reference, or whose base equipment is not an exported product, gets no system; a circuit with no loads whose category is not read is recognised by its class. Measured against the reference on seven files of Revit 2021, 2023, 2024 and 2025 with electrical circuits: every unnamed system of the reference is written with the same GlobalId and the same member, 128 of 128 (Revit 2024 and the Revit 2021 file of the same model), 131 of 131 (2023), 208 of 208 (2025, and two files of the second model in 2021 and 2023) and 445 of 445 (2025), and no unnamed system is written that the reference does not have. Members were compared by element tag. Named systems are unchanged in the five files where this was compared (names and member tags), the duct and pipe file without circuits is unchanged, and the JSON output of those files is byte-identical to the build before. Not covered: a Revit 2022 file with 187 circuits gets none of the 128 unnamed systems of the reference because the reader holds no decoded record of its circuits; no file of Revit 2026 or of a release before 2021 with circuits was measured. The legacy profile writes no circuit systems; its output for one Revit 2024 file (the 128-system one) is identical to the build before outside the FILE_NAME line, and other files were not measured in that profile.

  • IFC export, current profile: the two devices whose connectors sit on the two ends of one wire (an OST_Wire element that the connectors name by a reference of type 1 to its end 0 and end 1) are joined by a pair of IfcDistributionPort (SOURCEANDSINK, with an IfcRelConnectsPorts, the in port on the device at end 0) instead of a free port on each connector. A wire is used when each end has one device, the two devices differ and the connectors have the same number at both ends; it is found by the category stored in its record, as the derived category of a wire in a Revit 2025 file is another one. The pair of two devices is written once, so a wire between devices that are already connected writes none. These connectors keep their free ports: those of that wire (unless a pair written for another wire between the same two connectors holds them), those of a wire whose other end has no exported device, and a connector that names only a circuit. Pairs are written for both orders of the two device ids, while the pairs of the reference are consistent with a rule that writes one only when the device at end 0 has the lower id: the output is a superset of the reference, with 182 more pairs on each of three samples (Revit 2022 and 2024 basic, Revit 2023 advanced MEP) and 213 more on a Revit 2025 electrical sample, all for real wires. Writing only the pairs with the lower id at end 0 was measured too: it gives the reference's IfcRelConnectsPorts by GlobalId on the electrical sample (592 of 592) and the reference's count on the other three. Ports of the reference that we lack fall from 1461, 1517, 1515 and 352 to 143, 199, 197 and 92 (in the order of the samples above, electrical last); ports we write that it does not have fall from 982, 1034, 1032 and 541 to 459, 511, 509 and 451, of which 364, 364, 364 and 426 belong to the added pairs. Two other MEP samples (Revit 2021 and 2023) are unchanged, and the In and Out ports of duct fittings that we write as SOURCEANDSINK are not changed.

  • IFC export, current profile: the ports of a connection between two duct or pipe fittings (and between a fitting and a host whose connector stores no direction) are in and out ports instead of bidirectional ones when the flow direction can be derived. A fitting connector that stores neither a direction nor a system takes the opposite of the direction of the connector it references, and the other connector of a fitting with two connectors takes the opposite of its partner, repeated until nothing changes, starting from the connectors that belong to a system and store a direction; a fitting with three or more connectors does not pass a direction between its own connectors, and a fitting connector that is paired with a connector of a system that stores the direction 0 (its own bidirectional calculation) is given none. Measured against the reference: on a Revit 2024 file 170 of the 181 directional connections between two fittings were bidirectional (98 with the GlobalIds of the reference, 72 with other ones) and now all 3112 port pairs equal the reference; the ports that differ from it drop from 199 missing and 511 extra to 55 missing (ports of the reference that have no host) and 367 extra (364 from the wire-connected pairs, a superset, and 3 single ports). Port pairs equal to the reference (same GlobalId and the same flow on both ports): the same project saved as Revit 2023 2969 -> 3045 of 3119, a Revit 2022 file 3037 -> 3112 of 3112, a Revit 2021 file 2598 -> 2661 of 2661 (ports that differ: 74 missing and 60 extra -> 14 missing and 0 extra), another Revit 2023 file 3016 -> 3028 of 3032, a Revit 2025 file 2046 -> 2048 of 2048 (all 4133 ports carry the GlobalId of the reference); a Revit 2025 electrical file is unchanged. Not solved: 33 connections of the Revit 2023 file whose fitting connectors store a direction or system value that no connector can have, and 41 further connections of that file (2 in the other Revit 2023 file) that are still bidirectional where the reference has an in and an out port; free connectors of fittings and of equipment that the reference gives a direction (331 ports of the Revit 2021 file, 83 and 25 of the two Revit 2023 files); and the 55 ports of the reference that have no host. The legacy profile is unchanged.

  • IFC export, current profile: a slab written as an extrusion whose outline has arcs (the sides over them are vertical cylinders, as in a floor with rounded corners) gets GrossVolume in BaseQuantities: the area of its top surface (the same GrossArea as before) times the depth of the extrusion. Before, such a slab had no GrossVolume, because the search for its prism stopped at any curved face. A slab whose body is a brep (a pile cap of round plan) and a shell with a curved side that is not a vertical cylinder are unchanged. Perimeter is not written for these slabs: the reference's value is the length of the outline with its arcs counted as arcs in one sample (exact on one slab of a Revit 2022 file) and as the corners they round in another (257 of 259 slabs of a Revit 2021 structural file, where each fillet counts as its two straight legs), and no feature of the slab told the two apart. Measured against the reference on six samples (one Revit 2021 structural file, one Revit 2022 file, one Revit 2023 file and three others): 263 GrossVolume slots become equal (259 in the Revit 2021 structural file, 2 in the Revit 2022 file, 1 in the Revit 2023 file and 1 in another sample; the two other samples are unchanged), no slot regressed, and the JSON output of the six samples is unchanged. The legacy profile does not reach this code and was not measured; the files of the other releases were not measured. 29 slabs of the Revit 2021 structural file that the reference gives a GrossVolume had none; 26 of them (hollow round piles) get one from the hollow pile entry below, and the 3 others (slabs with holes) still have none, with a GrossArea that also differs from the reference.

  • IFC export, current profile: a floor whose type has a validated own name takes that name for the Reference property of the floor (Pset_SlabCommon, Pset_QuantityTakeOff, Pset_ReinforcementBarPitchOfSlab) and, through it, for the type part of the floor name, instead of the last string of the type block. The name is validated as the layers of the type are: the type has one root block of the floor class whose category and discriminator agree with it, a unique id and a decoded compound structure; a Reference parameter of the floor or of the type still comes first. A type that carries text parameters after its name ends in one of them, so the floors of one such type in a Revit 2021 architectural file were named after a text parameter of the type instead of after the type. Measured by GlobalId against the reference: Revit 2021 architectural file 6 floors, 18 Reference slots now equal, none regressed; Revit 2025 architectural file 6 floors (18 Reference slots, 6 names and 6 object types now equal), Revit 2025 structural file 5 floors (15, 5 and 5), Revit 2023 architectural file 3 floors (9, 3 and 3); in these files only those floors change, and no slot regressed. Seven further samples (Revit 2020 to 2022) are unchanged, and the JSON output of all the samples measured is unchanged. In the Revit 2021 file the floor name itself is still not equal to the reference, because the system family name part of it differs (pending in a separate change); in the Revit 2023 and 2025 files it is equal. The legacy profile is unchanged: the Revit 2021 architectural file exported with it is identical, line by line apart from the FILE_NAME line, before and after this change.

  • Default output, current IFC profile: a slab whose body is a swept extrusion of a hollow circle (a tube-shaped foundation slab) gets Perimeter, the circumference of the outer circle, Width, its diameter, and GrossVolume, the area of its top surface (the area of the ring) times the depth of the extrusion. Measured in one Revit 2021 structural file: 26 slabs, 26 Perimeter and 26 Width slots now equal to the reference, none regressed, columns, beams and all other products unchanged. The 26 GrossVolume slots are new and differ from the reference on purpose: the reference GrossArea of these slabs is the sum of the areas of the outer and the inner circle, computed with an approximate pi (3.14163 to 3.14169), and its GrossVolume is that area times the depth, which is 1.72 to 1.77 times the volume of the whole cylinder of the outer circle and so cannot belong to a tube. The ring is the physical shape: the profile has a wall of 60 mm (outer radius 400 or 500 mm), the reference NetArea equals our GrossArea (the ring) and the reference NetVolume equals ours in all 26 slabs, and our GrossVolume is 1.0001 to 1.0018 times that NetVolume. GrossArea of these slabs is the area of the ring and stays different from the reference. Three other slabs of that file (slabs with holes) remain different (geometry), and still have no GrossVolume; not measured on other files.

  • IFC export, current profile: a slab written as an extrusion whose outline has arcs (the sides over them are vertical cylinders, as in a floor with rounded corners) gets GrossVolume in BaseQuantities: the area of its top surface (the same GrossArea as before) times the depth of the extrusion. Before, such a slab had no GrossVolume, because the search for its prism stopped at any curved face. A slab whose body is a brep (a pile cap of round plan) and a shell with a curved side that is not a vertical cylinder are unchanged. Perimeter is not written for these slabs: the reference's value is the length of the outline with its arcs counted as arcs in one sample (exact on one slab of a Revit 2022 file) and as the corners they round in another (257 of 259 slabs of a Revit 2021 structural file, where each fillet counts as its two straight legs), and no feature of the slab told the two apart. Measured against the reference on six samples (one Revit 2021 structural file, one Revit 2022 file, one Revit 2023 file and three others): 263 GrossVolume slots become equal (259 in the Revit 2021 structural file, 2 in the Revit 2022 file, 1 in the Revit 2023 file and 1 in another sample; the two other samples are unchanged), no slot regressed, and the JSON output of the six samples is unchanged. The legacy profile does not reach this code and was not measured; the files of the other releases were not measured. 29 slabs of the Revit 2021 structural file still have no GrossVolume; their GrossArea also differs from the reference, and this change does not touch them.

  • IFC export, current profile: the OperationType of an IfcDoorStyle is the Operation parameter of the door type when its text is a value of the door style operation enumeration (for example SINGLE_SWING_RIGHT or DOUBLE_DOOR_SLIDING); before, it was always NOTDEFINED. A value that ends in _LEFT or _RIGHT is written for the door as it is placed: the other side when the hand flip and the facing flip of the door differ (a mirrored door), the stored side when they are equal. The two flips are now read from the family instance record of the door (reader, internal; the JSON output does not carry them). A sided value of a door whose flips are not read stays NOTDEFINED, and so do a style whose doors give different values and any other text of the parameter. Measured against the reference on a Revit 2024 sample and on the Revit 2023 save of the same project: in each file 12 of the 41 door styles of the reference get its value (11 of the 23 written door styles change), no slot regressed and nothing else in the file changes; the other 29 stay NOTDEFINED. In the Revit 2024 file the rule decides 18 of the 54 doors (7 with a value without a side, 7 sided with equal flips, 4 sided with different flips, all four SINGLE_SWING_RIGHT written as SINGLE_SWING_LEFT) and agrees with the reference for all 18; the other 36 doors have a sided value and no flips, because the reader records no family instance record for them. The rule is confirmed by this one project, and the swap was seen on single swing doors only: no sliding, folding or double swing door with read flips was measured. A Revit 2023 sample whose door types have no Operation text (the reference has values there) and a Revit 2025 sample are unchanged. Window styles, ConstructionType, the legacy profile and the JSON output are not touched.

  • IFC export, current profile: the railings of files with their own class schema (Revit 2021 and 2023 and later) get Length in BaseQuantities, as those of a Revit 2022 file do: the length stored in the railing's own symbol, taken under the same checks as before (the railing names its symbol, the symbol names the railing back, each is stored in one block, and the value is positive and finite). The value was already decoded in those files and always refused: the check that the railing and its symbol are each stored once read a table in which the schema index of the file and the stream walk had both counted the same block, and it now reads the count of the walk. Measured against the reference: 22 of 22 railings of a Revit 2023 sample and 9 of 14 of a Revit 2024 MEP sample now carry the Length of the reference where none did before, with no regressed slot and no railing added or lost; so do 34 and 33 of 39 in the Revit 2024 and Revit 2023 files of a larger architectural project and 29 of 39 in a Revit 2025 sample, again with no regressed slot and no product added or lost; 10 of 10 railings of a Revit 2021 sample carry it (no export of that sample from the commit before was compared). The IFC and JSON output of a Revit 2022 sample and the JSON output of the Revit 2023 and 2024 samples are unchanged. Not covered: the 5 railings of the Revit 2024 MEP sample that still have no Length are stair railings whose own block is longer than that of the others and gives no symbol as it is read now (the same 5 have none in the Revit 2022 file of that model); a railing whose stored length is not positive gets no Length; Pset_RailingCommon.Height and the names of the railings of Revit 2020 and 2021 files are not changed; the railings without Length in the three larger files (5, 6 and 10) were not traced. Revit 2020 and 2026 files and the legacy profile were not measured.

  • IFC export, current profile: a room or space whose level is not a building storey (a level with the building storey flag off) is written as an IfcSpace of the highest building storey at or below that level instead of being left out; where no storey is that low it is still not written. Measured against the reference, together with the next entry: on the Revit 2024 and 2023 versions of one sample project 18 spaces appear in each (66 to 84 written; all 81 spaces of the reference are present, 18 were missing), each in the storey the reference gives it, and the 3 spaces the reference does not have are the same 3 as before; on a Revit 2025 MEP sample 17 spaces appear (105 to 122 of 122), each in the reference's storey, none extra. The name, long name, type, Pset_SpaceCommon.Reference, base quantities, extrusion depth and model height of the body of the appeared spaces equal the reference, except one appeared space of the Revit 2023 file, whose plan area is not read: it has no body and only Height of the quantities. One element that the reference contains in an appeared space moves to that space in each of the two versions of the first project; no other container changes and no slot regressed. The rule was derived from these two projects and is not confirmed elsewhere; the output of a Revit 2023 and of a Revit 2025 architectural sample is unchanged, and no space appears in a Revit 2024 MEP sample.

  • IFC export, current profile: Pset_SpaceCommon of a room carries FloorCovering, WallCovering and CeilingCovering (IfcLabel) from the floor, wall and ceiling finish of the room, each written when the finish has a text, and Height of BaseQuantities (the height of the storey) is written also for a room without a plan area, which had no quantities. Measured against the reference: on the Revit 2024 and 2023 versions of one sample project FloorCovering equals the reference on 81 of 81 spaces in each (none was written), WallCovering and CeilingCovering on the one space that has them, and no other space has them in either output; on a Revit 2024 MEP sample Height equals the reference on 163 of 163 spaces (160 before). No slot regressed on the six files measured. The coverings are confirmed by that one project only: no other reference file at hand has a covering. A finish stored as an empty text writes no property, and no such room with a reference value was observed. The areas and the volume of a room without a plan area are still not written (3 spaces of the Revit 2024 MEP sample).

  • IFC export, current profile: the GrossVolume and OuterSurfaceArea of a column written as an extrusion are the length times the profile area and the length times the perimeter with the length and the perimeter counted in the length unit of the file. Before, both were always counted in millimetres, so in a file in metres or feet GrossVolume was 1000 times and OuterSurfaceArea 1e6 times the value of the reference. A file in millimetres is unchanged: there the two values keep those factors over cubic and square metres, as the reference has them. Length, CrossSectionArea and NetVolume are not changed. Measured against the reference on two files in metres (Revit 2024 and Revit 2023, 139 columns each, 54 of them with these two quantities, the same counts in both) and one file in feet (Revit 2023, 12 such columns); two values count as equal when |a-b| <= 1e-6*max(1,|a|,|b|), which is an absolute 1e-6 for values below 1. In each metre file GrossVolume becomes equal on 26 columns and OuterSurfaceArea on 29 (none was equal before), in the foot file OuterSurfaceArea becomes equal on 6 round columns; no slot regressed and no slot other than these two changed. Of the 26 GrossVolume values of a metre file, 14 equal the reference at a strictly relative 1e-6 too; the other 12 (columns of a square hollow profile, each below 0.05 m3) are 2.4e-6 to 2.6e-6 relative below it (ratio 0.9999974 to 0.9999976), as their CrossSectionArea is. The 29 and the 6 OuterSurfaceArea values equal the reference at a strictly relative 1e-6 as well. Not equal after the change: the GrossVolume of a round column (3 in each metre file, 6 in the foot file) is 1.9e-5 relative below the reference, because the reference's CrossSectionArea of a circle is 1.000019164 times pi r^2 and this exporter writes pi r^2; a column whose profile has fillets (24 I-shapes in each metre file, 6 in the foot file) has a GrossVolume 6e-4 to 9e-4 relative above and an OuterSurfaceArea 1e-4 to 2e-4 relative below the reference, as its CrossSectionArea and perimeter are; one tube column in each metre file has none of these quantities in the reference, where it is not an extrusion, and keeps them here. The IFC and JSON output of three files in millimetres (Revit 2022, 2023 and 2024) is unchanged. The legacy profile does not reach this code and was not measured.

  • Default output, current IFC profile: an element of any category whose level is a Level that is not a project storey is contained in the highest storey at or below that level, and in the building when no storey is that low, instead of the storey chosen by the bottom of its body. Before, only columns and building-services elements followed this rule. The level is the one already read for a level that is a storey: the base constraint of a wall, a roof or a stair and the base, schedule-only and reference level of a family instance (a nested instance reads its top-level instance) first, then the level, the schedule level and the other level parameters of the element; within each of the two groups a level that is a storey outranks one that is not, and a level of the first group that is not a storey outranks the second group. The assembly of a beam system stays in the storey of its first exported beam. Only where that beam was placed by a level that is not a storey is the assembly contained where the same rule puts the beam system itself: it stores no level and has no body, so that is the storey of the bottom of its own outline (a beam system without an outline stays with its first beam). Measured by the container of the products that both the output and the reference write. In a Revit 2024 sample 467 of 1968 products were in another container than in the reference, now 269: 198 agree now (103 walls, 30 slabs, 28 members, 10 building element proxies, 9 beams, 7 roofs, 6 curtain walls, 4 stairs, 1 railing) and none moved away from the reference; its two beam-system assemblies stay in the storey of the reference, one by the outline and one by its first beam. In the Revit 2023 save of the same project 471 of 2008, now 272 (199 agree now, none moved away). An architectural and a structural Revit 2023 sample (the structural one has 13 beam systems), a Revit 2025 sample and a building-services sample are unchanged: the IFC is identical to the build before outside the FILE_NAME line. The rule is confirmed by the reference in that one project only (its two saves); the unchanged files neither confirm nor contradict it. Not covered: 77 beams of the first project that store no level, 58 of them in another storey than in the reference, which puts all 77 in one storey whatever their height; 12 structural connections hosted on beams, in the storey below the one of the reference; the beam-system assembly of the architectural Revit 2023 sample, whose beams store no level, which stays with its first beam in a lower storey than in the reference; the products that one side contains in a space and the other in a storey; the 56 reinforcing bars that the reference aggregates in a parts assembly. For 2 of the 7 roofs that moved to the storey of the reference the level that moved them is not established: the JSON output shows no level parameter on them. The building container of an element below the lowest storey is not checked against Revit. The legacy profile is unchanged.

  • Reading: a block of the element stream is accepted only when the four bytes behind its payload repeat the length in its header. Before, that check was skipped for some blocks, so bytes inside a larger block that happened to look like a block of a known class were read as one, and the elements stored behind them in the same stream were not read. In the samples measured this brings back 36 grids and one wall opening of one Revit 2024 file (all in the reference) and 17 wall openings of one Revit 2025 file that the reference has; in that file 37 openings that the reference does not have are gone and 22 others that it does not have appear. Five beams of a Revit 2023 file are now read with their drive curve and get Pset_BeamCommon.Slope 0, where the reference writes no Slope for them. In the JSON output of a Revit 2022 test file 8 elements (4 graphics styles, 3 line patterns and one fill pattern) that were written without a class name are now read and written like the other elements of their class.

  • IFC export, current profile: the GlobalId of the opening of a written door or window is derived from the insert's own level (its level parameter) instead of the level of the storey that contains the insert. The storey level is still used when the insert has no level of its own, and the opening of an insert that is not written keeps the level of its host. The two levels differ for an insert whose own level is not a building storey, so its opening had a GlobalId that the reference does not have. Measured against the reference on a Revit 2024 sample and the Revit 2023 sample of the same project: in each file the openings of 8 doors now carry the GlobalId of the reference (openings equal to the reference by GlobalId: 29 -> 37 and 30 -> 38 of its 50; openings that the reference does not have: 10 -> 2 and 9 -> 1), no opening of the reference is lost and the number of entities of every class is unchanged. In the Revit 2024 file the level in the GlobalId of all 25 filled openings of the reference is the insert's own level. Not covered in these two files: 13 and 12 openings of the reference are still missing (one door opening that the reference names after the wall and ours after the door, one door whose opening is not written, and 11 and 10 unfilled wall openings). A Revit 2025 sample is unchanged (IFC identical), because none of its door openings takes this identity: 80 of them keep the identity of a cut opening where the reference has the door identity. Two conditions stop them, both seen in that one file only and left as they are: the number of cut loops of the door type is not read from that file, and its doors and door types hold the export switch parameter at 0, which is taken for a class override. In that file 48 door openings of the reference whose host is an IfcWall and 9 unfilled openings are also missing, and 5 unfilled openings are written that the reference does not have. The legacy profile is unchanged.

  • IFC export, current profile: the bars of a Rebar whose shape is a joggle are written: a shape driven bar whose shape has three edges and no hooks whose two bends are each less than a quarter turn and turn in opposite directions (the 10 degree S-bend); the solver refused every corner that was not a quarter turn. The dimension of an edge runs between the outer corners of its two ends, so the edge of the centre line polygon is sqrt(L^2 - lateral^2) - d/2 (t(i) + t(i+1)) long, where d is the bar diameter, t = tan(angle / 2) of the bend (0 at the two ends) and lateral is one diameter for the middle edge and 0 for the first and the last edge; the first point lies half a diameter inside the first edge on the side of its turn, and the bend arcs are tangent to the polygon as for quarter turns. A shape with a bend of less than a quarter turn that is not such a joggle (a single such bend, two such bends that turn the same way, one next to a quarter turn, more than three edges) or that has a hook, a bend of more than a quarter turn, and a corner whose turn does not follow its sign give no bar, as before; no such shape was measured. Shape data that is not valid (a direction, a length, a projection or a diameter that is not a finite number, a turn other than -1, 0 or 1, a diameter not smaller than the vector between the outer corners of the middle edge) gives no bar instead of an error. Measured on the Revit 2024 sample and on the Revit 2026 sample of the same project (124 Rebar in each, identical result): the 10 bars of the S-bend shape are written, all present in the expected output, none extra, and their centre lines (global coordinates, arcs by centre, radius and sweep) agree with it within 4e-9 m, with equal bar length, name, bar type, diameter and area; 117 of the 124 Rebar are written now (107 before). The other seven are still missing (an earlier analysis, not repeated in this work, found six with a variable distribution and one spiral). On the Revit 2021 version of that project the same 10 S-bend bars are written with the same GlobalIds as the expected output and none extra (IfcReinforcingBar 107 -> 117 of the 180 of the expected output); the 14 bars of its three containers are not written there (see the next entry). The IFC output of the basic structural samples of Revit 2020, 2021, 2022, 2023 and 2027, of the advanced structural samples of Revit 2022 and 2023 and of a Revit 2025 structural sample is byte for byte the same as before (the FILE_NAME line excluded), and so is their JSON output in every mode that was run (all of them but the Revit 2020 sample, and the Revit 2022 basic sample in its short mode). The rule is checked on one project (bar diameters of 12 and 20 mm in the unit test, one S-bend shape in the samples); other joggle shapes and shapes whose middle edge has no fixed direction were not measured. Bars exist only in the current profile, so the legacy profile is unchanged by construction (it was not run).

  • IFC export, current profile: the items of a RebarContainer are written as IfcReinforcingBar, one per item, named Structural Rebar Container:{container type}:{container id}: {item index} with the object type Structural Rebar Container:{container type}, the container id as tag, the GlobalId of the sub-element item index + 4 of the container and one swept disk along the centre line of the item; a container without an export id gives its items and its group GlobalIds of their own (derived from the container id and the item index), never the same one twice. All items of a container share one Pset_QuantityTakeOff (Reference = the container type) and are the members of one IfcGroup named Structural Rebar Container:{container type} that carries the GlobalId of the container; each bar is contained in a storey by its own lowest point, as the other bars are. The items are stored in the coordinates of the instance that hosts the container (a family instance), so the container is written only when that instance has a frame (FamilyInstance.m_instOrigin, m_RefDir, m_zAxis as an orthonormal frame); otherwise nothing is written for it. Items of a shape whose middle edge is not given by a direction but by its length and two projections (a joggle) are solved from the vector between the outer corners of that edge, whose direction is the one that leaves one diameter between the two outer corners across the edge. Measured on the Revit 2024 sample and on the Revit 2026 sample of the same project (3 containers, 14 items; identical result in both): all 14 are written, present in the expected output with the same GlobalId, name, object type, bar length, diameter, area, group GlobalId and name and Pset_QuantityTakeOff, none extra, each contained once, and the centre lines of all 131 bars of the file agree with it within 4e-9 m (arcs by centre, radius and sweep); 131 of the 180 bars of the expected output are written now (107 before this and the previous entry, 117 with the previous entry). The other 49 (the 42 RebarInSystem and the seven Rebar of the previous entry) are still missing. Measured on that one project and one kind of host (a family instance); containers of other hosts were not measured. The Revit 2021 version of the project holds the same three containers in the expected output, but none of their bars is written (the reason was not investigated), and no Revit 2022 file with a container was measured. The legacy profile writes no bars and is unchanged by construction (it was not run).

  • IFC export, current profile: a ramp that is written as one body takes the lowest vertex of its body (in the frame of its storey) as the location of its placement, with the placement of the body left as the identity under it and the vertices shifted by that minimum, instead of placing the ramp at the origin of the storey with the vertices in absolute coordinates. Measured by GlobalId on the three files of the one project among the test files that has ramps: the 2 ramps of each file (6 of 6) have the absolute location of the reference (0 of 6 before). Not measured on any other project.

  • IFC export, current profile: a roof soffit (OST_RoofSoffit) is an IfcCovering with no predefined type ($; the attribute is optional there) and its type an IfcCoveringType with the predefined type NOTDEFINED (the attribute is required on the type), instead of an IfcBuildingElementProxy. Measured by GlobalId on the three soffits of the one project among the test files that has them (two of its three files): class and PredefinedType of the soffit equal to the reference on 3 of 3 (0 of 3 before). The PredefinedType of the two soffit types is not equal to the reference, which leaves it empty (2 of 2). Open: a soffit whose type name is not read is named Element {id} where the reference has the type name (1 of 3; its Name and ObjectType change and still differ), and the two soffit types do not have the GlobalId of the reference. Not measured on any other project.

  • IFC export, current profile: a gutter (OST_Gutter) is an IfcFlowSegment with an IfcPipeSegmentType of its own (GUTTER) instead of an IfcBuildingElementProxy. The type is named Gutter:{type name}:{type id}, tagged with the id of the gutter, carries its name as ElementType, and has the GlobalId SubElementId(ExportId of the gutter, 1). Measured by GlobalId on the one gutter of the one project among the test files that has one (three files): the class and every attribute of the gutter, and the name, tag, ElementType, PredefinedType and GlobalId of its type, equal the reference (0 of 1 before). Not written: the representation map of the type and the Length of Pset_FlowSegmentPipeSegment and Pset_FlowSegmentDuctSegment of the gutter. Not measured on any other project.

  • IFC export, current profile: the Width of a standard-case wall in BaseQuantities is the total width of the layers of its wall type (the sum of the layer widths of its compound structure) when the type holds decoded layers; before, it was the width of the extruded profile, and a wall without decoded layers keeps that. GrossVolume (Width times GrossSideArea) and, where it is Width times the length of the location curve (a simple rectangular wall, an arc wall or a wall without a location curve), GrossFootprintArea follow the same Width. NetVolume does not use the layers of the type: the net volume of an arc wall (its footprint times its height) keeps the footprint of the width of the extruded profile, as before. Measured by GlobalId against the expected export (relative tolerance 1e-6): in one architectural sample (782 standard-case walls with quantities) the Width, GrossFootprintArea and GrossVolume of 9 walls of one retaining-wall type, whose single layer is thicker than their solid, become equal (0 regressed; Width and GrossVolume are then equal on all 782 walls, GrossFootprintArea on all but 2 that differ by about 2e-6 relative and are not changed), and Height, Length, GrossSideArea, NetSideArea and NetVolume stay equal on all 782; in a second sample 3 more walls gain the same three slots (9 slots, none regressed). Twenty-six further samples that have walls show no wall quantity differing by more than 1e-6 relative from the build before (one of them has no expected export and was compared with the build before only); values can differ in the last digits, as a width of 280.00000000000102 mm is now 280. The legacy profile does not reach this code and was not measured. Not changed: in 4 walls of two samples the expected GrossSideArea is the outer face plus its holes, where NetSideArea is the outer face less them (the other walls with holes, 136 faces in one of the samples, have equal values), and nothing the reader decodes (order, number or sense of the loops, share of the holes in the face) separates the two groups, so those GrossSideArea and GrossVolume values stay as before; a fifth wall, whose side face is one U-shaped loop without a hole, has an expected GrossSideArea that the decoded loops do not give; the NetVolume of one wall differs from the expected value by 2e-4 relative, where two faces of the solid give a shared corner that differs by 2e-4 ft, and that of another wall by 4e-6 relative.

  • IFC export, current profile: a multistory stair is repeated on each level it is copied to. Copy k (from 2) is an IfcStair without representation, contained in the storey of that level and named <stair name> : k, which aggregates copies of the stair's flights, landings, stringers and railings. The copies stand on new placements moved by the distance between the copy level and the base level of the stair, whichever storey holds the stair (a base level that is not a building storey counts with the height its storey placement would have), and share the representation items of the originals; their GlobalIds are derived from the GlobalId of the original and the copy number. The copy of the i-th exported support takes its GlobalId from the i-th support of the stair's whole support set, exported or not, as the reference does, and keeps the name and tag of the support it repeats. The copy levels are read from the stair's multistory data. On a Revit 2022 sample 69 elements that were missing are now written with the same GlobalId and tag as in the reference: 5 IfcStair, 10 IfcStairFlight, 5 IfcSlab, 36 IfcMember and 13 IfcRailing. On samples with stairs copied to four levels the copies of stairs, flights, landings and supports have the GlobalIds of the reference; the tag of a support copy differs from the reference where the stair has a support that is not exported, and the copies of railings the original stair does not aggregate are still missing. The copies carry no property sets, quantities, type or material relations yet. Nothing is repeated when the stair is not contained in a storey, when it has no base level of a known height, when a storey placement is rotated or does not hang from the placement the stair stands on, or for a component placed under anything other than the stair or that placement. A copy level that is not exported as a storey gets no copy.

  • IFC export, current profile: the BaseQuantities of a beam whose body is an extrusion of a profile with arcs (the fillets of a rolled section, the corners of a cold-formed one) are measured with those arcs. The outline of such a profile is written as a polyline with every arc as a run of equal chords of at most a sixteenth of a half turn, and the quantities were read from that polygon: CrossSectionArea and GrossVolume were off by 1e-4 to 3e-3 (relative), mostly too large, and OuterSurfaceArea was 5e-5 to 2e-4 too small, as was GrossSurfaceArea on all but a few beams. The writer of the profile now passes on, with the outline, which of its chords it made from which arc (the point the arc starts at, the number of its chords, its angle and the side it bulges to), and the beam quantities measure those arcs: the perimeter takes the arc in place of each of its chords and the area gains or loses the segment between the two, by the side the arc bulges to. Nothing is read out of the shape of the outline. An outline for which no arc is passed on is measured as its polygon, as before, however regular it is (a straight-sided polygon of 32 equal sides stays a polygon), and an arc written as one or two chords is measured like any other. Measured against the reference on four structural samples (two of Revit 2021, one of Revit 2023 and one of Revit 2025), each against an export of the commit the branch starts from: 1312 beam slots become equal (44 and 502, 40, 726), no slot regressed, the IFC differs only in these four quantities of 115, 602, 12 and 421 beams and the JSON output is identical. On the 375 of those beams for which the reference writes the quantities (13, 129, 12 and 221) CrossSectionArea, OuterSurfaceArea and GrossSurfaceArea are now all equal within the tolerance of the comparison (a difference of no more than 1e-6 of the larger value, and no more than 1e-6 for values below 1); 12 CrossSectionArea and none of the two surfaces were before. The perimeter agrees with the reference to 2e-14 (relative; the largest difference of OuterSurfaceArea in a sample is 1.4e-15, 1.44e-14, 9.6e-16 and 4.8e-15). The area agrees only within the tolerance: the area of the reference differs from the exact one by up to 6.0e-6 (ours less the reference is -1.94e-6 to +6.01e-6 of it on the 375 beams), no formula was found for that difference, and the exact area is written. GrossVolume is equal on 217 of the 375 (18 before); on the other 158, all in the Revit 2025 sample, it stays 1.1e-6 to 6.0e-6 above the reference. The other 775 changed beams repeat a body for which the reference writes these quantities once; they keep the full set and take the same measure, with nothing in the reference to compare it to (on 473 of them, in one Revit 2021 sample, the reference has a CrossSectionArea of 60 to 3067 times the section; ours is kept). Two architectural samples (Revit 2023 and 2024), each against an earlier export of the main branch: 74 and 94 beam slots become equal, no slot regressed and the IFC differs only in these four quantities of 54 and 59 beams; the four quantities are now equal on all 19 and 24 beams for which the reference writes them (only GrossVolume of 2 beams of each was before), and the other 35 beams of each repeat such a body. Not covered: the inner loops of a profile with voids (only the outer loop is corrected); an outline whose arcs the writer does not all find in it (none is passed on, the outline keeps its polygon); an outline that is one arc closed by one straight side, a half disc or a circular segment (the writer cannot tell which way round such an arc lies and lays its chords over the straight side, which this change leaves as it is: no arc is passed on and the section is the polygon as written, as before); a profile that is not written from the end face of the solid of the beam (no arcs are passed on there); columns and slabs (not changed); a profile whose area is taken from its nominal dimensions (left as it was). An arc of one or two chords and an outline closed by one straight side are covered by tests only: neither changes a value in the six samples. CrossSectionArea of an IfcMember that takes the beam rule was not exercised: no IfcMember changed in any of the six samples. The legacy profile was not measured.

  • IFC export, current profile: the room of a family instance that stores none is looked up at one test point per instance instead of at the centre of its box. An instance of a family with a room calculation point is tested at that point, placed with the instance, whatever its host. Otherwise a wall-hosted instance is tested one foot outside the side face of its wall that it faces (the face with the higher handle, the lower one when the instance is flipped across the wall), a work-plane-based instance one foot from its placed origin along its own Z axis, an instance of a hosted family on a floor or a ceiling one foot from its placed origin against its own Z axis (along it when the instance is flipped in Y), and any other instance at the centre of the box of its own geometry (the placed box of its symbol when it has none), moved up to half a foot towards its level. Only the rooms of the phase the instance was created in are considered (the phase id as the room record stores it when the room has no validated phase, 0 included), and the first room by id whose vertical extent and solid hold the point wins; a point on the boundary is inside. A room stored with the instance is kept, as before. Measured against the reference by the container of the products on nine sample files: 376 containers now agree (239, 31, 31, 29, 16, 14, 12, 3 and 1), and no slot regressed in eight of the files. In the ninth, 10 instances (4 building element proxies and 6 furnishing elements) moved from the storey to a space where the reference keeps the storey. They were then compared with the room Revit itself reports: the 4 proxies have no host and Revit gives them that room (the reference moves their test point to a level elevation counted from the project base point, which is 100 mm off in that model), so they stay in the space; the 6 furnishing elements are hosted by a floor and Revit gives them no room, which the floor rule above now follows. With the floor rule that file has 6 more containers that agree with the reference and 1 that no longer does (a floor-hosted furnishing element the reference puts in a space and Revit does not); the IFC of five other of the nine files does not change, the remaining three were not exported again. Three more rules come from the same comparison with Revit. Of the 504 ceiling-hosted fixtures of that model Revit gives the room under the ceiling to the 396 that are not flipped and no room to the 108 that are flipped in Y, which the ceiling rule above follows; the reference puts all of them in a space. Two furnishing elements whose symbol stores a box wider than its geometry are in the room that holds the centre of the geometry, where the centre of the stored box lies 1.4 mm outside it; the reference keeps them in the storey. An instance of a family with a room calculation point is in the room that holds that point, for every kind of host: the point is read from the symbol and agrees with the one Revit reports for the 453 instances of that model that have one, and 2 level-based fixtures that stood in a space are in the storey, as in Revit. These three rules change 112 containers of that file (108, 2 and 2), which now differ from the reference; the IFC of five other files and the JSON output of four do not change. Against the room Revit reports for the family instances of that model in its Revit 2022 version, measured again on the current main branch, the container agrees for 1408 of 1530 contained instances (1274 on the main branch, 1291 for the reference); in four synthetic files made for the floor, the mirrored, the calculation point and the boundary cases for 80 of 80, 8 of 8, 32 of 32 and 76 of 78. Not followed: Revit counts a point 0.05 mm outside the plan boundary of a room as inside (the 2 of 78). An instance hosted by an arc wall gets no wall-side point and is tested as an instance without a host. The JSON output of all measured files is unchanged; the IFC of a tenth, synthetic file is identical to before. The legacy profile was not measured.

  • Adds rvtio coverage <file> [--report report.json] [--top n], which counts how many records of the partition streams of one file the class schema of that file walks to their last byte, per block kind (project or loaded family), channel and class, with the reason and the field at which a walk stops, and lists every stream of the file with its kind and whether it frames or decodes. The count is strict: no byte scan, no resynchronisation, no recovery; a partition stream whose framing does not hold adds no record and is named in the report. It writes nothing but the JSON report (rvtio-coverage/1). Exit code 0 when the file was measured, 1 for a wrong command line, 2 when the file cannot be read. See the coverage report.

Changed

  • Reading: in a file without its own class schema (Revit 2020 and 2022) whose partition streams are read by their framing, the root object of each element, from which the reader takes the class of the element and the stair and railing facts it keeps per root, is the channel-102 record of the element in the project's own block, in its current copy. Before, the stream walk counted every block it met for the id, including copies of the id in the documents of loaded families, and missed roots it did not reach; an element with two counted roots or none had no class. On 23 such files 59 elements in 7 files now get their class (a schedule, a view, a wall, a rebar, a sketch plane and others): in the compact JSON they carry the name of their class and their usual properties instead of an empty name, and the export report names their class instead of unknown; the IFC of all 23 files is unchanged. Refs #47.
  • Reading: the element index of the files with their own class schema (the header category, design option and outline of each element, the class of its root object and the field walk of that object) and the reinforcement reader read a partition stream whose framing holds by its records: the channel-101 records of the project's own block are the element headers and its channel-102 records the root objects, one of each per element. Before, a scan of the stream took any byte run that looked like an item, including the records of the documents of loaded families, whose element ids repeat project ids; a value on which such a copy differed was dropped. On 29 files the scan found 5,987,630 header-like and 6,640,008 block-like items, the framing gives 848,325 of each. A stream whose framing does not hold is scanned as before. On 52 project files of the releases 2020 to 2027 the IFC and the export report are identical to the output of the main branch; in the compact JSON of one Revit 2021 file one element (the pen width table) now gets its name and properties, as in the other files.
  • Reading: a partition stream whose framing holds (content blocks, segments, gzip members with their CRC and length, record lengths, the owners of each block) is cut into its segments by that framing; the search for gzip members and its recovery of damaged members are kept for a stream whose framing does not hold, and the diagnostics say which way a stream was read. On 52 project files of the releases 2020 to 2027 the segments are the chunks the search found, byte for byte, in all 434 partition streams; the IFC, the compact JSON and the export report of 14 of them, one or more of every release, are identical to the output of the main branch.
  • Field walk by the class schema of the file: a text is read as long as its count says, where the walk stopped at a text longer than 100,000 characters; a text longer than the rest of the record stops it as any read past the end does. rvtio coverage also reads an extensible storage entity in the block of a loaded family by the schemas of that family's own document: up to release 2024 a family keeps them in its ADocument in Global/ContentDocuments (one document per content block, under the key of the block), from release 2025 Global/Latest holds them. On 52 project files of the releases 2020 to 2027 every record of every partition stream is now walked to its last byte (29,794,710 records; 117 were not: 45 at a long text, 72 at an entity of a family document); no class row has fewer exact records. Output: of ten files that hold such a text, eight family types of lighting fixtures in two MEP files get their parameter list in the compact JSON, and in three files of one MEP project one view type and one sun study change the reason they are left out of the IFC for; the IFC of the ten files does not change.
  • Default output, IFC and JSON: the readers behind the output read the values of an extensible storage entity by the schema of that entity, as rvtio coverage does, so the objects behind those values are read in their place instead of out of step, and the record of an element that keeps an entity is read to its end. An entity whose schema the file does not hold, or with a field type that is not read, is passed as before. The parameters that a store behind an entity would list as unset are still left out, as before; what that rule would give now is not measured. The bars of a rebar system that holds an entity are read now as well. A RebarInSystem that is the one RebarInSystem of its system takes the GlobalId of the system, as in the reference; the bar of a system with more keeps its own sub-element GlobalId (the number of its bars plus 3). Measured on 28 project files of the releases 2020 to 2026 against the output of the main branch, and for the IFC also against the reference: no IFC value became worse. On eight MEP files the in/out direction and the name of 887 distribution ports are now those of the reference (331, 331, 83, 83, 25, 25, 8 and 1), and on ten MEP files 3,221 assignments of a port or an element to a system are added that the reference has; 48 added assignments are not in the reference at that system (in four files; not checked in Revit). 6 systems the reference has are written that were missing; 26 pipe fitting types that the reference does not have are no longer written, and 9 others it does not have either are (types of a single fitting each, whose GlobalId changes with the parameters now read). In three files of one architectural project (releases 2021, 2023 and 2024) 42, 48 and 48 reinforcing bars that were left out are written, all with the GlobalId of the reference (in the file of release 2024, 26 of the 42 bars of its systems are the one bar of their system); the three files now write 159, 168 and 168 of the 169 bars of the reference (117, 120 and 120 before). The one bar of a system is not put into a group of its system, which it stands for. In the compact JSON 1,214 family instances of that project (three files) and 3,086 of another (one file) get the parameter list that the other instances of their categories already had, and 53 family types of one MEP file get theirs. Against Revit, on 172 of the 1,214 in the file of release 2021, every value the compact JSON writes as Revit shows it (type, mark, comments, phases, heights, levels, offsets and others, 1,561 values) is equal, and the fields the compact JSON writes in its own form (family, type and host as names, the design option as None, justifications as numbers, an empty level as None) are written as for 170 instances of the same categories that had their list before. The IFC of 16 of the 28 files and the JSON of 23 do not change.
  • The repository moved to the IOExtend organization. The project and repository URLs of the packages, the links in the documentation and the address of the documentation site (https://ioextend.github.io/RvtIO/) name the new place. The old repository address redirects to the new one; the old address of the documentation site does not.
  • rvtio coverage reads the values of an extensible storage entity by the schema of that entity. ESEntity.m_blob is an owned pointer whose class tag is not written: a handle of 0 is no entity and nothing follows; behind 0xFFFFFFFF stands the 16-byte GUID of the entity's schema where the class tag would be, and the values are queued like any owned object, so they lie behind every object queued earlier and in front of the objects queued later. The values are the fields of that schema in the order of ESField.m_entryIndex (the schema lists them by name), each one value, a count and the values of an array, or a count and the pairs of a map, of the type ESField.m_fieldTypeName names: a text, a scalar, or a class of the class schema read inline, among them an entity, which queues its own values. The entity schemas are read from Global/Latest, an ADocument that the same walk reads: ESSchemaStorage.m_storedSchemas in the class schemas up to release 2024, ESSchemaStorage.m_schemaUsageMap from release 2025. Before, the walk passed an entity as one without values, read the objects behind the values out of step and named the stop after whatever class came next. On 52 project files of the releases 2020 to 2027 the records that are not exact go from 27,847 to 117 of 29,794,710, and every FamilyInstance record of the project blocks is exact (24,044 of 131,261 were not); 154 class rows have more exact records, in files of every release but 2026, whose one file gains nothing, and no class row of any channel of any file has fewer. Left are 72 records in the documents of loaded families whose entity names a schema that Global/Latest of the project does not hold (the new reason entity schema not stored) and 45 records that stop at the 100,000 character limit of a text (4 of them inside the values of an entity). When the entity schemas of a file cannot be read, every entity with values stops its record with entity schema not stored instead of being passed; a handle other than 0 and 0xFFFFFFFF stops with entity handle, and a schema with a field the walk does not read with entity field type (neither occurs in the 52 files). Some kinds of values are read by the same grammar although no record of those files holds one (short, int64_t, a single char, arrays of bool, float and GUIDvalue, and maps that occur only empty). The readers of the export are not given the entity schemas and read an entity as before; their output was not compared. rvtio coverage <file> --trace <class|owner,...> is new: a developer aid that prints the walk of single records (the queued objects with their offsets, the fields of the class the walk stopped in, the bytes around the stop). See the coverage report.

Fixed

  • IFC export, current profile: a project whose length format is "metres and centimetres" declares metres, and a project in US survey feet declares feet; both were written in millimetres before. The IFC export of Revit itself declares the same for these two formats. In a file of the Revit 2020 layout US survey feet are now read as a length format too (not checked on such a file). A project in any other format (centimetres, decimetres, inches) is still written in millimetres, with every length scaled to them, and the diagnostics now say that its stored unit has no unit family of its own instead of presenting millimetres as that unit. The README and the API reference name the rule and the default. Measured with Revit 2022 on one model under ten length formats, declared unit and the formats that give it: metres (metres; metres and centimetres), centimetres (centimetres), millimetres (millimetres), feet (feet; feet and fractional inches; US survey feet; decimetres), inches (inches; fractional inches). Four small files saved by Revit 2022 in metres and centimetres, US survey feet, centimetres and inches export as described. (Closes #36)
  • Export report: export.skippedByClass did not list every element the IFC export left out. Three exits of the product loop dropped a product without an entry: a product without bounds (product-without-bounds), one with invalid bounds (invalid-bounds) and one whose box is under 1 cm in every direction (bounds-under-one-centimetre). A curtain wall or roof that is written after its parts and has no written part is now listed as container-without-written-parts, and the elements the export leaves out before that loop as non-primary-design-option and outside-export-phase. The list of reasons in docs/export-report.md gains these six and the four the exporter already wrote without naming them there (export-to-ifc-flag-no, reveal-sweep, product-without-body, wall-without-solid); a test keeps the list complete, and the documentation says that levels, rooms and the site topography are listed although their spatial entity is written. The IFC output does not change; the counts considered and skipped of a report grow by the elements that were not listed: on two samples measured, 1 product without bounds in one and 1137 elements of non-primary design options in the other, with the same exported count as before. New tests start the command line tool as a process on an input that is no compound file, on an empty one and on a compound file cut in half, and check exit code 2 and the failed report; exit code 3 for a reader limit is still tested in the library only. (Closes #37)
  • A Global or Formats stream whose gzip member has a corrupt deflate header is treated as a stream that does not decode: a class table damaged that way is reported as unreadable input (exit code 2 of downgrade and coverage) instead of ending in an unhandled exception.
  • Field walk by the class schema of the file: a class reference (field type 10, the only one is ClassDefinitionRef.m_ref) is read as its 2-byte class tag; it was read as 8 bytes, which put the walk of every element header record out of step at ElementHeader.m_classDef. rvtio coverage now counts every record of channel 101 as exact, in the project block and in the family documents, on 52 project files of the releases 2020 to 2027 (9,931,570 records; none was exact before). The counts of channels 102 and 103 do not change on any of the 52 files. The IFC output of six sample files and the JSON output of four are identical to the output of the main branch. The width table of --schema-check gives the type as 2 bytes as well.