Rust API Reference¶
API documentation for the Rust crates.
Note: For the crates published to crates.io, full generated rustdoc with source links lives on docs.rs (linked per crate below). Locally,
cargo doc --openbuilds the same documentation for the whole workspace.
Workspace¶
All crates live in one Cargo workspace (versioned together, MPL-2.0 licensed):
| Crate | Path | Rustdoc | Description |
|---|---|---|---|
ifc-lite-core |
rust/core |
docs.rs | High-performance IFC/STEP parser for building data |
ifc-lite-geometry |
rust/geometry |
docs.rs | Geometry processing and mesh generation for IFC models |
ifc-lite-processing |
rust/processing |
docs.rs | Shared IFC processing pipeline and types used by server and FFI |
ifc-lite-export |
rust/export |
local cargo doc |
Domain-format exporters (HBJSON, OBJ, glTF/GLB, CSV, JSON, JSON-LD, STEP/IFC, IFC5/IFCX, Merged, Parquet/.bos) |
ifc-lite-clash |
rust/clash |
docs.rs | High-performance geometry kernel for IFC clash detection |
ifc-lite-ffi |
rust/ffi |
docs.rs | C FFI bindings: native cdylib for in-process IFC parsing |
ifc-lite-wasm |
rust/wasm-bindings |
docs.rs | WebAssembly bindings for IFC-Lite |
The Python bindings (rust/python, PyPI package ifclite-geom) are excluded from the workspace and documented on the Python API page.
ifc-lite-core¶
Core parsing functionality.
Modules¶
pub mod parser; // STEP tokenization and entity scanning
pub mod decoder; // Lazy entity decoding + entity index
pub mod generated; // Generated IFC type enumeration (IfcType)
pub mod schema_gen; // Decoded attribute values and entities
pub mod streaming; // Streaming parse events
pub mod fast_parse; // Fast single-purpose extraction helpers
pub mod georef; // Georeferencing extraction
pub mod legacy_entities; // IFC2X3 legacy entity handling
pub mod model_bounds; // Model/placement bounds scanning
pub mod project_units; // Project unit resolution
pub mod step_encoding; // STEP string encode/decode
pub mod units; // Unit conversion helpers
pub mod error; // Error types
Parser Module¶
ifc_lite_core::declared_schema_bounded(ifc_bytes) reads the first actual
FILE_SCHEMA identifier in the first 64 KiB of the STEP header. It skips
comments and quoted decoys and stops at the header section boundary, returning
None when no declaration is found in that window.
Token¶
/// STEP token types
#[derive(Debug, Clone, PartialEq)]
pub enum Token<'a> {
/// Entity reference: #123
EntityRef(u32),
/// String literal: 'text'
String(&'a [u8]),
/// Integer: 42
Integer(i64),
/// Float: 3.14
Float(f64),
/// Enum: .TRUE., .FALSE., .UNKNOWN.
Enum(&'a [u8]),
/// List: (1, 2, 3)
List(Vec<Token<'a>>),
/// Typed value: IFCPARAMETERVALUE(0.), IFCBOOLEAN(.T.)
TypedValue(&'a [u8], Vec<Token<'a>>),
/// Null value: $
Null,
/// Asterisk (derived value): *
Derived,
}
EntityScanner¶
/// Scans IFC file for entity locations
pub struct EntityScanner<'a> {
// ...
}
impl<'a> EntityScanner<'a> {
/// Create a scanner over the file bytes
pub fn new<T>(content: &'a T) -> Self;
/// Create a scanner starting at a byte position.
///
/// Preconditions: `position` is a GLOBAL byte offset into `content` (the
/// returned entity spans are absolute, not shard-relative), and it must sit
/// at a known entity boundary — typically the byte right after a `;\n`
/// terminator, not inside the HEADER section or partway through an entity.
/// `new_at` does NOT auto-skip the STEP header; that is the caller's
/// responsibility (used by the sharded-scan pre-pass). Starting mid-header
/// or mid-entity yields incorrect spans. The offset is clamped to the buffer
/// length.
pub fn new_at<T>(content: &'a T, position: usize) -> Self;
/// Advance to the next entity: (express_id, type_name, start, end)
pub fn next_entity(&mut self) -> Option<(u32, &'a str, usize, usize)>;
}
parse_entity / entity_count¶
/// Parse a single entity definition into (express_id, type, attribute tokens)
pub fn parse_entity<'a, T>(input: &'a T) -> Result<(u32, IfcType, Vec<Token<'a>>)>;
/// Cheap O(scan), O(1)-memory entity tally (no index allocation)
pub fn entity_count<T>(content: &T) -> usize;
Generated Schema Module¶
IfcType¶
/// IFC entity types: all 876 entity types from the IFC4X3 schema,
/// plus Unknown(u32) storing a CRC32 hash of unrecognized names.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IfcType {
IfcBoxedHalfSpace,
IfcBridge,
IfcBuilding,
IfcBuildingStorey,
// ... 876 variants ...
/// Unknown/unrecognized IFC type (stores CRC32 hash)
Unknown(u32),
}
/// Every entity type the schema declares, in declaration order, re-exported
/// from `ifc_lite_core` as `IFC_TYPES`.
///
/// The enum is exhaustive but not enumerable — `Unknown(u32)` makes it open and
/// the ids are sparse — so anything reasoning about the WHOLE schema (mapping
/// every class into another vocabulary, auditing coverage, generating a table)
/// needs this rather than re-parsing the EXPRESS file.
pub static ALL: &[IfcType];
impl IfcType {
/// Parse from an (upper-case) type name
pub fn from_str(s: &str) -> Self;
/// Parse from the stable numeric type id
pub fn from_id(id: u32) -> Self;
/// Stable numeric type id
pub fn id(&self) -> u32;
/// Type name (SCREAMING case as written in STEP)
pub fn as_str(&self) -> &'static str;
/// Type name (CamelCase)
pub fn name(&self) -> &'static str;
/// This entity's attributes, in STEP declaration order — the order
/// `DecodedEntity::get` indexes. Supertype attributes come first, so
/// position 0 is `IfcRoot::GlobalId` on every rooted entity.
///
/// These names are the GENERATED schema's (IFC4X3). An entity whose
/// attribute list grew between IFC releases has a different length, and
/// possibly different indices, in a file declaring an older `FILE_SCHEMA` —
/// so the positions match `DecodedEntity::get` for that file only when its
/// schema matches. Check `FILE_SCHEMA` before treating an index from here
/// as authoritative.
pub fn attribute_names(&self) -> &'static [&'static str];
/// The position of a named attribute, for `DecodedEntity::get`.
/// Case-sensitive; EXPRESS names are PascalCase.
pub fn attribute_index(&self, name: &str) -> Option<usize>;
/// Direct supertype in the schema hierarchy
pub fn parent(&self) -> Option<Self>;
/// Walk the hierarchy: is self a subtype of parent?
pub fn is_subtype_of(&self, parent: Self) -> bool;
/// Whether the entity is abstract in the schema
pub fn is_abstract(&self) -> bool;
}
Schema-aware attribute names¶
Use the file's declared schema when positional attribute names must match the
source STEP record. The helper is re-exported from ifc_lite_core; entity names
are case-insensitive, while returned EXPRESS attribute names retain their exact
PascalCase spelling.
pub fn attribute_names_for_schema(
file_schema: &str,
entity_name: &str,
) -> Option<&'static [&'static str]>;
file_schema accepts the bundled IFC2X3, IFC4, and IFC4X3 family labels,
including standard _ADDn, _TCn, and _RCn revision suffixes. Revision
suffixes are normalized to one fixed bundled registry per family (the IFC4
registry is generated from IFC4_ADD2_TC1), so other accepted suffixes are
family-level approximations and may not match the source file's positional
attributes. It returns None for an unknown schema family or an entity that
the selected registry does not declare; an entity declared with no attributes
returns Some(&[]). Do not fall back to IfcType::attribute_names() after
None, because that method describes the canonical generated IFC4X3 schema
and could mislabel an older record's positional values.
has_geometry_by_name¶
/// Check if entity type typically has geometry (cached, name-based)
pub fn has_geometry_by_name(type_name: &str) -> bool;
Decoder Module¶
EntityIndex / build_entity_index¶
/// Index of entity byte ranges in the file: express_id -> (start, end)
pub type EntityIndex = FxHashMap<u32, (usize, usize)>;
/// Build the index in one scan over the file bytes
pub fn build_entity_index<T>(content: &T) -> EntityIndex;
ColumnarEntityIndex (wasm workers)¶
Wasm geometry workers keep the shared entity index as sorted u32 columns
(ids / starts / lengths) and look up by binary_search instead of
materializing a per-worker FxHashMap (#1682). Native / server paths still
use EntityIndex.
pub struct ColumnarEntityIndex { /* ids, starts, lengths */ }
impl ColumnarEntityIndex {
pub fn from_columns(ids: &[u32], starts: &[u32], lengths: &[u32]) -> Self;
pub fn from_scan<T>(content: &T) -> Self;
pub fn from_hashmap(map: &EntityIndex) -> Self;
pub fn from_hashmap_consuming(map: EntityIndex) -> Self;
pub fn lookup(&self, id: u32) -> Option<(usize, usize)>;
}
EntityDecoder¶
/// Entity decoder for lazy parsing from raw IFC bytes,
/// with per-decoder caches (decoded entities, points, placements, units).
pub struct EntityDecoder<'a> {
// ...
}
impl<'a> EntityDecoder<'a> {
/// Create decoder over the file bytes (index built lazily)
pub fn new<T>(content: &'a T) -> Self;
/// Create decoder with a pre-built index
pub fn with_index<T>(content: &'a T, index: EntityIndex) -> Self;
pub fn with_arc_index<T>(content: &'a T, index: Arc<EntityIndex>) -> Self;
/// Wasm path: attach a shared columnar index (binary-search lookup)
pub fn with_arc_columnar_index<T>(content: &'a T, index: Arc<ColumnarEntityIndex>) -> Self;
/// Install a direct-address index for a source with u32 byte offsets
pub fn set_dense_index(&mut self, index: Arc<DenseEntityIndex>);
/// Decode an entity by express id (cached)
pub fn decode_by_id(&mut self, entity_id: u32) -> Result<DecodedEntity>;
/// Decode an entity by byte range
pub fn decode_at(&mut self, start: usize, end: usize) -> Result<DecodedEntity>;
/// Length-unit scale of the file (metres per file unit), cached
pub fn length_unit_scale(&mut self) -> f64;
}
The decoder also exposes fast single-purpose accessors used by the geometry pipeline (get_cartesian_point_fast, get_polyloop_coords_cached, get_entity_ref_list_fast, ...); see rust/core/src/decoder.rs.
DenseEntityIndex::try_from_columns(ids, starts, lengths) accepts sorted, unique
IDs and u32 span columns. It returns None if the columns disagree or direct
addressing would allocate more than compact columns. lookup(id) preserves
missing IDs and empty spans. Install an index only on a decoder for the same
source. Native loading selects this representation when suitable, uses compact
sorted columns for sparse IDs, and retains the hash-index path for sources whose
offsets exceed u32. Caller-supplied Arc<EntityIndex> values remain supported.
Compact staging has a source-based row budget; denser record streams switch to
hash indexing during the scan, coalescing duplicate IDs as they arrive.
Streaming Module¶
ParseEvent¶
/// Events emitted during streaming parse
#[derive(Debug)]
pub enum ParseEvent {
/// Parsing started
Started { file_size: usize, timestamp: f64 },
/// Entity discovered during scanning
EntityScanned { id: u32, ifc_type: IfcType, position: usize },
/// Geometry processing completed for an entity
GeometryReady { id: u32, vertex_count: usize, triangle_count: usize },
/// Progress update
Progress {
phase: String,
percent: f32,
entities_processed: usize,
total_entities: usize,
},
/// Parsing completed
Completed { duration_ms: f64, entity_count: usize, triangle_count: usize },
/// Error (non-fatal)
Error { message: String, position: Option<usize> },
}
StreamConfig¶
/// Streaming parser configuration
#[derive(Debug, Clone)]
pub struct StreamConfig {
/// Yield progress events every N entities
pub progress_interval: usize,
/// Skip these entity types during scanning
pub skip_types: Vec<IfcType>,
/// Only process these entity types (if specified)
pub only_types: Option<Vec<IfcType>>,
}
// Default: progress_interval = 100; skip_types = owner history,
// person, organization, application; only_types = None.
parse_stream¶
/// Stream IFC file parsing with events (async Stream)
pub fn parse_stream<T>(
content: &T,
config: StreamConfig,
) -> Pin<Box<dyn Stream<Item = ParseEvent> + '_>>;
Schema Gen Module¶
AttributeValue¶
/// Decoded attribute value
#[derive(Debug, Clone)]
pub enum AttributeValue {
EntityRef(u32),
String(String),
Integer(i64),
Float(f64),
Enum(String),
List(Vec<AttributeValue>),
Null,
Derived,
}
impl AttributeValue {
pub fn from_token(token: &Token) -> Self;
pub fn as_entity_ref(&self) -> Option<u32>;
pub fn as_string(&self) -> Option<&str>;
pub fn as_enum(&self) -> Option<&str>;
pub fn as_float(&self) -> Option<f64>;
pub fn as_int(&self) -> Option<i64>;
pub fn as_list(&self) -> Option<&[AttributeValue]>;
}
DecodedEntity¶
/// Fully decoded entity
#[derive(Debug)]
pub struct DecodedEntity {
pub id: u32,
pub ifc_type: IfcType,
pub attributes: std::sync::Arc<Vec<AttributeValue>>,
}
Error Module¶
/// Parser error type
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Parse error at position {position}: {message}")]
ParseError { position: usize, message: String },
#[error("Invalid entity reference: #{0}")]
InvalidEntityRef(u32),
#[error("Invalid IFC type: {0}")]
InvalidIfcType(String),
#[error("Unexpected token at position {position}: expected {expected}, got {got}")]
UnexpectedToken { position: usize, expected: String, got: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("UTF-8 error: {0}")]
Utf8(#[from] std::str::Utf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
ifc-lite-geometry¶
Geometry processing and mesh generation. CSG (void cutting, boolean clipping) runs on the in-tree pure-Rust exact mesh-arrangement kernel (src/kernel/) on every target, native and wasm32 alike.
ifc_lite_geometry::extract_swept_disk(entity, decoder) reads an authored
IfcSweptDiskSolid into AnalyticSweptDisk, with an ordered segments list of
AnalyticCurveSegment::Line and Arc values. The result is in representation-local
IFC length units; AnalyticStatus::Unsupported carries a reason and no partial
segments when the directrix cannot be described exactly. The processing API below
places those segments in product world coordinates.
extract_analytic_extrusion(entity, decoder) reads a source
IfcExtrudedAreaSolid without tessellation. Its AnalyticExtrusion retains the
authored SweptArea and Position STEP IDs, raw file-unit Depth, authored
IfcDirection.DirectionRatios, a derived unit axis, and a separate matrix for
the solid-local Position. extract_analytic_profile resolves exact closed
line and arc loops for rectangles, circles, and supported arbitrary profiles,
including holes, signed winding area, and exact perimeter in file units. The profile's own Position matrix
remains separate. Arbitrary boundaries also require conservative
topology validation, including contained,
disjoint holes; unresolved topology produces Unsupported. Unsupported profile geometry or malformed extrusion
parameters produce AnalyticStatus::Unsupported with a reason; no sampled
outline is substituted. ProfileType must be AREA, and
ValidExtrusionDirection rejects a direction perpendicular to the solid's
local Z axis. These are source-local, IFC Z-up file-unit values:
product placement, representation mapping, unit conversion to world metres,
and later CSG remain distinct steps.
Features¶
default = [] # no optional features
debug_geometry = [] # extra geometry debugging output
csg_capture = [] # measurement-only CSG corpus capture for benches
observability = [] # route diagnostics through `tracing` instead of eprintln
Key Types (crate-root re-exports)¶
Most modules are crate-private; consumers use the re-exports from ifc_lite_geometry::*.
Mesh¶
/// Triangle mesh representation
#[derive(Debug, Clone)]
pub struct Mesh {
/// Vertex positions (x, y, z)
pub positions: Vec<f32>,
/// Vertex normals (nx, ny, nz)
pub normals: Vec<f32>,
/// Triangle indices (i0, i1, i2)
pub indices: Vec<u32>,
/// Whether the RTC offset has already been subtracted from positions
pub rtc_applied: bool,
/// Per-mesh local origin (f64) in the RTC/world frame; when non-zero,
/// positions are stored relative to it for f32 precision
pub origin: [f64; 3],
/// Instancing side-channel; None on the flat path
pub instance_meta: Option<InstanceMeta>,
// ...
}
impl Mesh {
pub fn vertex_count(&self) -> usize;
pub fn triangle_count(&self) -> usize;
/// Axis-aligned bounds of positions
pub fn bounds(&self) -> (Point3<f32>, Point3<f32>);
}
Related mesh types: SubMesh, SubMeshCollection, InstanceMeta.
Profiles and extrusion¶
pub use profile::{Profile2D, Profile2DWithVoids, ProfileType, VoidInfo};
pub use profile_extractor::{extract_profiles, ExtractedProfile};
pub use extrusion::{extrude_profile, extrude_profile_lofted, extrude_profile_with_voids};
GeometryRouter and processors¶
/// Trait for geometry processors
pub trait GeometryProcessor {
/// Process entity into mesh. `quality` selects tessellation detail.
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
schema: &IfcSchema,
quality: TessellationQuality,
) -> Result<Mesh>;
/// Get supported IFC types
fn supported_types(&self) -> Vec<IfcType>;
}
/// Routes an entity to the processor registered for its IfcType
pub struct GeometryRouter {
// ...
}
Built-in processors (all re-exported at the crate root): ExtrudedAreaSolidProcessor, ExtrudedAreaSolidTaperedProcessor, FacetedBrepProcessor, AdvancedBrepProcessor, BooleanClippingProcessor, FaceBasedSurfaceModelProcessor, PolygonalFaceSetProcessor, RevolvedAreaSolidProcessor, SurfaceOfLinearExtrusionProcessor, SweptDiskSolidProcessor, TriangulatedFaceSetProcessor.
For routers processing batches from one immutable model,
GeometryRouter::new_brep_signature_cache() creates a SharedBrepSignatureCache.
Pass a clone to each router's enable_shared_brep_signature_cache to reuse
completed faceted-BREP signatures when item deduplication is enabled. Create a
new cache whenever the source model changes: signatures are keyed by express
ID. Tessellation quality, unit scale and RTC remain router-local and are folded
into the final deduplication key. The native processing pipeline manages this lifetime automatically.
GeometryRouter::with_scale_and_local_frame(unit_scale, enabled) fixes the
mesh-coordinate frame when the router is created. Use it when one operation
must produce identical local-frame geometry across native and wasm targets;
ordinary routers continue to use the target and environment default. The frame
choice cannot be changed after construction because router caches depend on it.
For repeated IfcMappedItem sources, mapping_origin_transform(source, decoder)
parses the source map's origin once. resolve_scaled_mapped_item_transform_with_origin_loader
accepts a loader for that matrix and still parses each item's MappingTarget
before invoking it; both methods use the same target-times-origin composition
as resolve_scaled_mapped_item_transform.
Other notable re-exports: orient_mesh_outward, calculate_normals, ClippingProcessor, Plane, Triangle (CSG), hash_mesh_world / GeometryHasher (geometry-diff hashing), instancing encode/decode helpers, and the nalgebra types Point2, Point3, Vector2, Vector3.
embedded_raster_dimensions(step_binary) returns optional PNG/JPEG dimensions
from an IFC binary literal without decoding pixels. It supports allocation
preflight; a readable header does not certify a complete or valid image stream.
ifc-lite-processing¶
Shared processing pipeline used by the native server, the FFI DLL, the Python bindings, and the WASM bindings. Parallelised with rayon on native targets.
Key re-exports:
// One-call pipeline: IFC bytes -> per-element meshes
pub use processor::{
process_geometry, process_geometry_filtered, process_geometry_with_index,
process_geometry_streaming, /* ...streaming variants with options... */
OpeningFilterMode, ProcessingResult, StreamingOptions,
};
// Analysis-ready export document (welded, Z-up, world metres)
pub use geometry_export::{build_geometry_data_export, ExportedElement, GeometryDataExport};
// Optional authored swept-disk checks and reusable swept-disk/extrusion sources
pub use analytic_export::{
check_swept_disk, extract_analytic_quantity_sources, AnalyticQuantitySources,
extract_swept_disk_descriptions, extract_swept_disk_definitions,
extract_extrusion_definitions, AnalyticSourceContext, AnalyticSourceKey,
extrusion_nominal_quantities, DirectrixMetrics, DirectrixSegmentMetrics,
ExtrusionDefinition, ExtrusionDefinitions, ExtrusionInstance,
ExtrusionNominalQuantities, SweptDiskDescriptions,
SweptDiskCheckError, SweptDiskCheckFinding, SweptDiskCheckOptions,
SweptDiskCheckReport, SweptDiskFindingCode, SweptDiskOccurrence,
SweptDiskDefinition, SweptDiskDefinitions,
SweptDiskInstance, SweptDiskSourceKey, SweptDiskSourceContext,
SweptDiskNominalQuantities,
};
pub use georeferencing::{
extract_georeferencing, extract_georeferencing_with_index, Georeferencing,
};
pub use ifc_lite_geometry::TessellationQuality;
pub use style::{default_color_for_type, Rgba};
pub use types::mesh::{InstanceRecord, MeshData, RawInstanceOccurrence};
pub use types::response::{CoordinateInfo, ModelMetadata, ParseResponse, ProcessingStats};
pub use parallel_scan::build_entity_index_parallel;
extract_swept_disk_descriptions(ifc_bytes, ids) returns the outer Radius,
optional InnerRadius, and ordered exact line/circular-arc Directrix for
supported IfcSweptDiskSolid items. Call disk.directrix_metrics() on an
occurrence to get Option<DirectrixMetrics>. A complete description returns
Some, with total_length and indexed segments containing centreline
length and optional bend_angle. Lengths, coordinates, and radii are
absolute IFC world metres, Z-up. An arc's
bend_angle is the positive sweep magnitude in radians; its signed
Directrix::Arc::sweep_angle retains travel direction. Line segments have no
bend angle. These geometric measurements do not include fabrication bend
allowances or deductions. ids optionally filters product STEP IDs.
disk.nominal_quantities() derives circular material cross-section area,
cross-section area × centreline length (nominal volume), and outer/optional
inner lateral areas. Areas are in m² and volume in m³. These are estimates of
the uncut source sweep: self-overlap and mitred joins can change the physical
body, so they are not IFC-authored IfcElementQuantity values or certified net
quantities. The method returns None for an unsupported description, CSG
operand, broken directrix join, degenerate segment, or arc whose radius does
not exceed the disk radius. A sharp but joined mitre remains a nominal estimate.
JSON and Python directrix exports include the same values under
nominal_quantities.
extrusion_nominal_quantities(&source_extrusion) derives exact net profile
area (outer boundary less holes), perpendicular height, and nominal volume
for a complete AnalyticExtrusion. For an oblique extrusion, perpendicular
height is Depth × |unit ExtrudedDirection.z| in the solid's local frame.
These values use raw IFC file-length units (squared and cubed as
appropriate), because the source extrusion has no product occurrence
transform. The function returns None for unsupported or invalid source
profiles; it does not claim a product's post-boolean net quantity.
Each record identifies its source solid and mapped-item path.
source_modified identifies an authored CSG operand whose sweep may differ
from the final body, so its metrics are source geometry measurements rather
than final fabricated quantities. It does not indicate cuts from external
IfcRelVoidsElement openings. Unsupported curves or transforms have an
explicit status, no partial directrix, and the method returns None. For an
unsupported transform, radii retain their authored values converted to metres;
no world circular radius is implied. The existing mesh export remains a
separate operation.
check_swept_disk(&occurrence, &options) checks the extracted source geometry
without decoding or tessellating it again. SweptDiskCheckOptions::default()
uses 1e-9 m for a zero-length segment, 1e-6 m for a disconnected join, and
1e-6 rad for a tangent discontinuity. The checker also flags a circular arc
whose centreline radius does not exceed the swept disk radius. A report's
findings identify the source segment, or both sides of a join, with a stable
SweptDiskFindingCode, measured value, threshold and units. An unsupported
analytic description produces a report with skipped_reason and no partial
findings. source_modified is carried into the report so an authored CSG
operand is not mistaken for the finished body. All tolerance values must be
finite and nonnegative; SweptDiskCheckOptions::validate() and the checker
return SweptDiskCheckError otherwise. These are geometric diagnostics, not
fabrication-code checks or bend-allowance calculations. IFC allows non-tangent
consecutive segments to form a miter, so a tangent discontinuity is an
inspection cue rather than an automatic schema violation
(IfcSweptDiskSolid).
extract_analytic_quantity_sources(ifc_bytes, ids) collects flattened
swept-disk descriptions, reusable swept-disk definitions, and reusable
extrusion definitions in one bounded canonical representation walk. Description
indices match source-instance ordinal values; the individual entry points
keep their existing output contracts.
extract_swept_disk_definitions(ifc_bytes, ids) provides an opt-in
source/instance form without changing the flattened result above. A source
contains authored Radius, InnerRadius, and Directrix in raw IFC file
length units. Its key includes the IFC-byte SHA-256, FILE_SCHEMA, exact f64
length-unit-scale bits, solid STEP id, and either the top-level representation
id or ordered IfcRepresentationMap ids. The bits are encoded as 16 hex
digits so JSON consumers preserve exact identity. Repeated mapped items that use the
same representation map share a source but remain separate instances with
deterministic ordinals and mapped-item paths. source_modified still marks CSG
operands.
extract_swept_disk_views returns the world descriptions and reusable raw
definitions together from one bounded walk and one decoded source cache. The
rebar schedule uses this paired view to retain complete world-space sweeps even
if the independent definition output budget omits a reusable source key.
Each instance's column-major f64 world_from_source maps raw source
coordinates directly into absolute IFC Z-up metres; it includes the file-unit
scale, product placement and nested mapped transforms. A source radius is not
a world radius until the uniform instance scale is applied. Nonuniform world
disks carry an unsupported instance status; invalid matrices have None and
an unsupported status. The source remains available for inspection. Work and
output budgets are reported in diagnostics when reached.
extract_extrusion_definitions(ifc_bytes, ids) uses the same bounded
representation walk to return exact IfcExtrudedAreaSolid source profiles and
placed product occurrences without tessellation. AnalyticSourceKey contains
the model SHA-256, schema, exact unit-scale bits, solid ID, and either a direct
representation ID or ordered representation-map IDs. Repeated mapping targets
share one ExtrusionDefinition while preserving separate ExtrusionInstance
ordinals, mapped-item paths, and f64 transforms.
Source ProfileType, DirectionRatios, Depth, profile loops, area, perimeter,
and profile/solid Position matrices remain in IFC file units. For a complete
source with valid positive net profile area, nominal_quantities reuses
extrusion_nominal_quantities to
report net profile area, projected height, and nominal volume in squared,
linear, and cubed IFC file-length units; unsupported or invalid sources yield None.
For a boundary point from a complete source, apply the profile
profile_position, then the extrusion position_matrix, then the instance
world_from_source; the last matrix maps to absolute IFC Z-up metres and
includes product placement, mapping, and file-unit scale. An absent optional
Position leaves its matrix field as None, so use identity for that step
when composing rather than expecting an identity array. Check source and
instance statuses before using an absent matrix: unsupported or tapered source
geometry and invalid or singular occurrence transforms have explicit statuses.
source_modified marks CSG operands, not final post-boolean geometry. Source
and instance output budgets are independent and report truncation in
diagnostics.
ifc_lite_export::build_rebar_schedule(ifc_bytes, ids, &options) returns one
row per selected IfcReinforcingBar entity, including rows whose body has no
supported swept-disk source. rows is keyed by occurrence STEP ID. Each row
exposes and serializes its IFC identity as GlobalId and Name, matching IfcRoot.
Every represented source sweep has its own ordinal, solid/directrix IDs, mapping path,
outer/inner radii, directrix metrics, geometric check report and an optional
reusable SweptDiskSourceKey. It keeps repeated mapped sources and CSG operands
distinct; repeated uses of one map share the source key. A missing key is
reported per row without dropping the world sweep. The authored map uses exact EXPRESS
attribute names; each entry identifies the occurrence or type entity that
supplied it. Numeric measures retain value_file_units and their value_si
conversion, while centreline lengths are separately derived world metres.
An authored CrossSectionArea of zero remains in the record with its provenance;
the row diagnostic states that it does not establish a physical section area.
Radii are effective world metres for complete paths; an unsupported transform
retains source radii in metres without implying a world circular radius.
Conflicting occurrence/type values are reported, with the occurrence taking
precedence. bar_entity_count and represented_sweep_count count IFC records,
not manufactured bars. The API provides no physical bar count, cutting length,
or certified fabrication result.
build_rebar_schedule_with_preflight(ifc_bytes, ids, &options, &limits) adds
caller-defined geometric comparisons to each represented sweep. Construct
RebarPreflightLimits::new(min_inside_bend_radius_m,
min_straight_segment_length_m, max_developed_centreline_length_m) with finite,
nonnegative SI-metre limits. Each comparison retains its measured value, limit,
segment index (when applicable), and pass/fail result; equality passes. The
inside radius is the centreline arc radius minus the swept outer radius.
Modified CSG sources, unsupported directrices, and paths with gaps or zero-length
segments receive a skip reason and comparisons: None; assessed sweeps have
comparisons: Some(...). The underlying source findings remain in checks.
Tangent discontinuities remain inspection cues and do not skip comparisons. Rows
without a swept-disk source receive preflight_skipped_reason. Missing line or
arc segments are listed as unassessed. These comparisons do not certify a
cutting length or fabrication-code compliance.
build_rebar_schedule_with_fabrication_precheck(ifc_bytes, ids, &options,
&policy) adds an opt-in RebarFabricationPolicy without changing the legacy
preflight API. Start with RebarFabricationPolicy::default() and set any subset
of finite nonnegative SI limits: minimum inside bend radius or straight segment
length, maximum developed centreline length, maximum absolute difference
between authored NominalDiameter and the geometric outer diameter, and an
inclusive finite, nonnegative bend-angle range [minimum, maximum] in radians.
The policy is validated before IFC decoding. Each requested check reports
pass, fail, or uncheckable, a source bar/solid/directrix ID, segment index
when applicable, measurement, threshold, units, and reason. The reason is
None for a passing check and explains a failure or unavailable measurement
otherwise. An absent arc,
straight segment, or positive authored NominalDiameter is uncheckable;
conflicting type assignments and occurrence/type diameter conflicts also make
the diameter comparison uncheckable. Authored diameter retains occurrence or
type provenance. Geometry comparisons require a complete world-circular
source; modified CSG, unsupported transforms and incomplete paths are
uncheckable. Bend angle is the magnitude of each directrix arc sweep, not a
join discontinuity. The result is always precheck_only: material, bending
process, allowances, jurisdiction and physical bar count remain unchecked.
Authored BarLength is not a verified cutting length. A reinforcing-bar entity
may represent more than one manufactured bar.
Appearance authoring¶
ifc_lite_processing::appearance::calibrate_appearance_plane establishes one
measured plane from two stable native-source landmarks and a known distance.
PlaneCalibrationRequest supplies the raster-to-source affine, raster extent
and an IFC Z-up world anchor/direction/normal. CalibratedPlane returns a
world-space Mapping::Planar, raster corners and metres per source unit.
Crop, page rotation and raster DPI only change the raster-to-source transform;
they do not change the retained calibration landmarks or measured distance.
This bounded calculation does not mutate IFC or composite pixels outside the
page. Invalid, sheared or unrepresentable planes fail explicitly. Reconstructed
world edges and anchor displacement must each preserve their intended vector
within one part per million; large origins never enlarge that tolerance.
ifc_lite_processing::appearance::plan_appearance prepares image and UV edits
against an effective IFC STEP snapshot. It shares canonical geometry production
with loading and returns both IFC edit operations and per-corner preview data.
It does not mutate the input. AppearanceRequest supplies an IFC4/IFC4X3 schema,
revision token, reserved allocator watermark, product IDs, relative image URI
and an existing-UV, planar or box mapping. The initial scope is direct, unshared
IfcTriangulatedFaceSet Body geometry with absent or complete IfcLocalPlacement
chains. IfcGridPlacement and IfcLinearPlacement, including local placements
parented to them, remain explicitly unsupported in every mapping mode; canonical
load-time placement recovery is not sufficient validation for writing edits.
Products with sliceable material-layer associations are also excluded, because
reopening may replace their face set with layer slices. Canonical comparisons
resolve the shared load-time RTC metadata before meshing georeferenced geometry.
Plans report unsupported products;
callers must explicitly accept a reduced scope, retain image resources and
apply the complete IFC edit plan atomically after revalidating the revision and
allocator. Preview consumers validate source topology and map triangle corners,
rather than assuming vertex counts establish UV correspondence. targetVertexCount
is the final canonical vertex-pool bound; removed triangles may leave unused
vertices before or after the surviving indices. Do not substitute corner count
or maximum-index-plus-one for this pool size.
targetCornerNormals carries final shading normals in renderer Y-up triangle-corner
order ([nx, nz, -ny] from IFC). Removing UV seams can merge near-coplanar weld
representatives and change shading normals while every triangle position remains
identical. Preview installs these canonical target normals so its shading matches
reopening; it must not retain the previous normals or relax position checks.
The exported ifc_lite_geometry::MAX_TEXTURE_DIMENSION is the shared raster edge
limit; appearance preflight rejects invalid pixel dimensions. Embedded PNG/JPEG
header inspection reads at most 1 MiB of encoded header bytes without copying the
full STEP binary literal or decoding pixels.
ifc-lite-export¶
Domain-format exporters. Every exporter takes IFC bytes (or already-produced meshes) and returns the serialized output.
pub use csv::{export_csv, CsvMode, CsvOptions};
pub use gltf::{export_glb_from_meshes, export_gltf_streaming, try_export_glb,
try_export_glb_with_stats, GltfOptions, GltfStats /* ... */};
// Every from-bytes GLB entry point is `try_`: an empty visible set is
// `ExportError::NoRenderGeometry`, never a zero-mesh GLB (which is not valid glTF).
// `GltfOptions::tessellation_quality` picks the curve tessellation density for
// the glTF paths, re-exporting `TessellationQuality` so a caller does not need
// a direct `ifc-lite-processing` dependency to name a level. The default is
// `Medium`, which is the golden-output identity every byte-comparison test in
// this crate rests on - coarser levels trade curve fidelity for vertex count
// on tube-heavy models, and DO change the emitted bytes.
pub use ifc_lite_processing::TessellationQuality;
pub use hbjson::Model;
pub use ifc5::{export_ifc5, Ifc5Options};
pub use json::{export_json, JsonOptions};
pub use jsonld::{export_jsonld, JsonLdOptions};
pub use kmz::{ifc_angle_to_kml_heading, try_export_kmz_collada_from_meshes, KmzOptions};
pub use merged::{export_merged, export_merged_with_stats, MergedOptions, MergedStats};
pub use obj::{export_obj, export_obj_with_stats, ObjOptions, ObjStats};
pub use step::{export_step, export_step_json, export_step_with_stats,
AttrMutation, PropMutation, StepOptions, StepStats};
// A `MutablePropertyView.exportMutations()` log, applied with byte parity to
// the TypeScript `StepExporter` (#5941). `MutationLog::from_json` reads the log.
pub use step_log::{export_step_with_log, export_step_with_log_to_writer,
export_merged_models_with_logs, MutationLog,
LogMutation, LogNewEntity, MutationKind, GeorefMutations,
LogExportStats, StepCounters};
pub use model::{build_export_model, stream_export_model, ExportModel /* ... */};
pub use quantity_analysis::{analyze_authored_quantities, AuthoredQuantityAnalysis /* ... */};
// Additional opt-in join: analyze_quantities(content, ids) -> QuantityAnalysis.
// It combines authored observations with nominal source occurrences; product_total
// is None with an aggregate diagnostic because overlap/voids/CSG are unknown.
// `ExportModel` and both streaming entry points carry the model's UnitScales.
// Attribute values are in the FILE's units, unlike the geometry exporters'
// output, which is normalised to metres — so a consumer writing a quantity
// beside exported geometry needs this to interpret it.
pub use ifc_lite_processing::prepass::UnitScales;
// Attribute access beyond the eight fields `EntityRow` carries. The callback
// receives the `DecodedEntity` each row was built from, borrowed — read any
// attribute from it without this crate guessing which ones matter.
pub use model::{stream_export_model_with_options, build_export_model_with_options,
ModelOptions, Placement};
pub use ifc_lite_core::{AttributeValue, DecodedEntity, IfcType};
analyze_authored_quantities(ifc_bytes, ids) is an opt-in, untessellated view
keyed by actual product STEP ID. It reuses the export model's physical-quantity
decoder and retains each IfcElementQuantity/leaf entity ID, exact EXPRESS
names, numeric authored value, kind, and occurrence or inherited type origin.
kind is the full IFC leaf type, such as IfcQuantityLength.
IfcRelDefinesByProperties.RelatingPropertyDefinition may name one definition
or an IFC4 IfcPropertySetDefinitionSet; every linked IfcElementQuantity is
retained. Malformed definitions and exhausted relationship, set-visit, or
authored-row, quantity-leaf-visit, aggregate quantity-set or leaf decode-byte, or
type-relationship work budgets appear in
diagnostics, with expansion stopped at the cap.
Per-record diagnostics are capped at 1,024 plus one truncation notice; distinct
work-budget refusal reasons remain visible after that cap.
It reports both sides of a conflict instead of applying the flattened row's
occurrence precedence. Explicit quantity units use the canonical bounded IFC
unit resolver; an unresolved or dimensionally mismatched unit has a diagnostic
instead of a guessed length-scale conversion. The IFC4X3 IfcQuantityNumber
is preserved too; Number and Count use an explicit named unit when supplied
and are otherwise dimensionless. product_count counts selected
IFC product entities, not represented solids or physical bars. Derived source
estimates remain separate from this authored view.
For Count and Number, distinct explicit unit entities are conservatively
reported as a conflict even when their display symbols match, since unit names
do not certify dimensional equivalence.
analyze_quantities(content, ids) joins those authored observations with the
canonical swept-disk and extrusion source/instance APIs. It keeps one source
record per geometric use, its source key and mapping path, plus formula, origin,
unit, limitation and status for each nominal measurement. Swept-disk directrix estimates are
in world metres; extrusion source estimates remain in raw IFC file units.
Depth is labelled as an authored solid parameter, separate from an authored
IfcElementQuantity. Product, source-use and unique-source counts are distinct.
product_total remains absent with aggregate_diagnostic, since overlap,
openings and CSG prevent a defensible final material total.
export_step_with_log applies the mutation log a MutablePropertyView
records (exportMutations()), replayed as importMutations replays it into a
view wired like the viewer's, and writes the file the TypeScript StepExporter
writes for it: byte-identical apart from the GlobalIds of generated records,
which are derived from the host and the new express id. Records the log does
not change stream from the source; memory beyond the record index grows with
the edits. A log carrying a mutation kind the writer does not apply yet, an
unrecognised mutation type, or an UPDATE_ATTRIBUTE with a null newValue
is an InvalidInput error (the TypeScript replay skips the latter two; a native
save refuses rather than drop an edit), as is combining a log with StepOptions::included or
with the per-edit vectors. LogExportStats reports the new and modified entity
counts the header states and the warnings the TypeScript exporter would give.
export_merged_models_with_logs(models, logs, opts) is export_merged_models
with a log per model: each edited model is first written through
export_step_with_log in its own schema, as MergedExporter.exportAsync bakes
edited models through StepExporter, and its georeferencing edits are not
applied (reported in MergedStats::warnings), as the TypeScript bake passes
none.
export_step_with_stats returns StepStats. total and written describe
the selected entity set. The remaining counters are validity or refusal
signals that callers should inspect: copies_refused counts copy-on-write
edits that were not emitted, refused_refs counts out-of-range STEP
references, and attribute_edits_refused counts root-attribute edits that
could not be applied. owner_history_unfilled counts records in an IFC2X3
downgrade that still have $ in the schema-required OwnerHistory slot, and
required_slots_unfilled counts the OTHER slots IFC2X3 requires a value in
that stayed $ because the schema offers no default that claims nothing (a
measure, a label, an entity reference, or an enum with no NOTDEFINED
member); slots whose declaration does offer one take .NOTDEFINED. or .F.
and are not counted. When either is non-zero, the emitted file is not valid
IFC2X3.
ModelOptions::default().with_placements(true) resolves each product's
ObjectPlacement into
EntityRow::placement. Off by default: the memo is ~128 B per distinct
placement, one per product in most files.
Placement is a column-major 4x4 whose translation is in metres, with no
RTC rebase and no Z-up to Y-up conversion — the IFC world frame, which is
the file's own frame and not the frame the glTF or OBJ exporters emit.
// Behind the `parquet-bos` feature (native only; kept out of the wasm bundle):
pub use parquet_bos::{export_bos, ParquetBosOptions};
Features¶
default = []
parquet-bos = [] # Parquet/.bos export (arrow + parquet + zip; not wasm32-safe)
observability = [] # surface faceted-brep phase timing for profiling examples
ifc-lite-clash¶
Native clash-detection kernel: a faithful port of the TypeScript reference engine in packages/clash (same AABB math, SAT triangle-triangle intersection, minimum-distance routines, per-element triangle BVHs, and narrow-phase classification). All computation in f64 over f32 input buffers.
pub use aabb::Aabb;
pub use narrow::ClashStatus;
pub use session::{ClashRecord, ClashSession, RuleResult};
use ifc_lite_clash::ClashSession;
let mut session = ClashSession::new();
// positions: concatenated per-element vertex coords (x, y, z, ...)
// pos_ranges: [float_offset, float_len] per element
// indices: concatenated per-element LOCAL triangle indices
// idx_ranges: [idx_offset, idx_len] per element
// aabbs: [minx, miny, minz, maxx, maxy, maxz] per element
session.ingest(&[], &[], &[], &[], &[]);
let result = session.run_rule(&[], &[], 0, 0.0, 0.0, false);
ifc-lite-ffi¶
C FFI bindings: a native cdylib for in-process IFC parsing (used by desktop-style hosts). By default it routes allocations through mimalloc (default = ["mimalloc"]; opt out with --no-default-features).
/// Parse an IFC file at `path` and write a serialized result buffer
pub unsafe extern "C" fn ifc_lite_parse(
path_ptr: *const u8, path_len: usize,
out_ptr: *mut *mut u8, out_len: *mut usize,
) -> i32;
/// Same, with an explicit opening-filter mode
/// (`opening_filter_mode`: 0 = Default, 1 = IgnoreAll, 2 = IgnoreOpaque)
pub unsafe extern "C" fn ifc_lite_parse_ex(
path_ptr: *const u8, path_len: usize,
opening_filter_mode: i32,
out_ptr: *mut *mut u8, out_len: *mut usize,
) -> i32;
/// Free a buffer returned by the parse calls
pub unsafe extern "C" fn ifc_lite_free(ptr: *mut u8, len: usize);
FFI contract. These entry points are unsafe and cross a C boundary, so the
caller owns the following guarantees:
- Inputs.
path_ptr/path_lendescribe a UTF-8 file path (need not be NUL-terminated).out_ptrandout_lenmust be non-null and valid for writes. A null pointer or non-UTF-8 path returns1and writes nothing. - Success (
0).*out_ptrreceives a heap buffer of*out_lenserialized JSON bytes. The buffer is owned by the caller from that point on. - Failure. Non-zero return codes leave the out-parameters untouched (no
buffer is allocated, so there is nothing to free):
1null pointer or invalid path,2file could not be read,3geometry processing panicked,4JSON serialization failed. - Freeing. Pass the exact
ptrandlenyou received from a successful parse toifc_lite_freeexactly once. Do not free on a non-zero return, do not free twice, and do not mix in a pointer/length from any other source.ifc_lite_freeis a no-op whenptris null orlenis0.
ifc-lite-wasm¶
WebAssembly bindings.
IfcAPI¶
The WASM surface is split across rust/wasm-bindings/src/api/*.rs; every method
is exported to JS under a camelCase js_name that mirrors pkg/ifc-lite.d.ts.
The methods below are representative, not exhaustive; see the
WASM API page for the full JS-side surface.
/// Main IFC-Lite API
#[wasm_bindgen]
pub struct IfcAPI {
// ...
}
#[wasm_bindgen]
impl IfcAPI {
/// Create and initialize the IFC API
#[wasm_bindgen(constructor)]
pub fn new() -> Self;
/// Fast SIMD entity scan; returns entity references for the data model
#[wasm_bindgen(js_name = scanEntitiesFast)]
pub fn scan_entities_fast(&self, content: &str) -> JsValue;
/// Streaming geometry pre-pass; emits progress via `on_event`
#[wasm_bindgen(js_name = buildPrePassStreaming)]
pub fn build_pre_pass_streaming(
&self,
data: &[u8],
on_event: &js_sys::Function,
chunk_size: u32,
disabled_type_names: Option<Vec<String>>,
skip_type_geometry: bool,
) -> Result<JsValue, JsValue>;
/// Mesh one batch of geometry jobs into a MeshCollection
#[wasm_bindgen(js_name = processGeometryBatch)]
pub fn process_geometry_batch(
&self,
data: &[u8],
jobs_flat: &[u32],
unit_scale: f64,
// ... RTC offset, void keys, styles, material colours
) -> MeshCollection;
/// Extract 2D profiles (outer boundary + holes) for parametric geometry
#[wasm_bindgen(js_name = extractProfiles)]
pub fn extract_profiles(&self, content: String, model_index: u32) -> ProfileCollection;
/// Export glTF binary (GLB) from the model
#[wasm_bindgen(js_name = exportGlb)]
pub fn export_glb(
&self,
content: &[u8],
include_metadata: bool,
hidden: &[u32],
isolated: &[u32],
hidden_types_csv: String,
lit: Option<bool>,
) -> Result<Vec<u8>, JsValue>;
/// Export CSV (entities / properties / quantities / spatial)
#[wasm_bindgen(js_name = exportCsv)]
pub fn export_csv(
&self,
content: &[u8],
mode: String,
delimiter: String,
include_properties: bool,
) -> Vec<u8>;
}
Features¶
default = ["console_error_panic_hook"]
threads = [] # threaded bundle via wasm-bindgen-rayon (built separately)
console-tracing = [] # forward structured diagnostics to the DevTools console
debug_geometry = [] # forwards to ifc-lite-geometry/debug_geometry
Building Documentation¶
Generate full Rustdoc documentation:
This will generate detailed documentation including:
- All public and private items
- Source code links
- Examples from doc comments
- Cross-references between items
ifc_lite_processing::appearance::catalog_appearance(bytes, &AppearanceCatalogRequest)
resolves rendered owner class and type selectors from an already-effective IFC4
or IFC4X3 snapshot. It returns sorted AppearanceCatalog products/types and an
explicit missing/ineligible ID list. IFC class strings use canonical PascalCase;
AppearanceCatalogType.name serializes as the exact EXPRESS attribute Name.
The function reuses appearance source decoding and its budgets, performs no meshing
or texture-image decoding, and rejects malformed relevant type assignments or oversized
catalog metadata without partial results. Hosts retain federation and selection
orchestration and must fence stale snapshot responses before use.
Finite page appearance¶
ifc_lite_processing::appearance::plan_page_appearance(source, &request, rgba)
adds a calibrated planar page while sampling each object's original albedo
outside the page. PageAppearanceRequest.appearance is the existing request with
Planar mapping and both repeat flags false. page and source_images[].raster
are {width, height, byte_offset, byte_length} ranges in a separate top-down,
straight-alpha RGBA8 payload. External images are keyed by their exact effective
IfcImageTexture.URLReference in image_uri; embedded textures are decoded by
the canonical Rust resolver. Missing external pixels are an explicit refusal.
The returned PageAppearancePlan contains the ordinary atomic plan, per-item
item_images, deduplicated assets with encoded PNG bytes, and the requested
texels_per_metre. Each generated URI is textures/<SHA256 of PNG bytes>.png.
The host must adopt all returned image resources and IFC edits as one command;
normal planning, undo and model export must retain those resources together.
The topology-preserving triangle atlas resamples the original appearance at the requested physical density, raised per triangle to preserve at least the source texture pixel frequency in every direction (including skewed or repeated UVs). Budget refusal never silently lowers that source-fidelity floor. Finite page bounds and straight-alpha compositing prevent repeated/clamped page borders outside the page. Sampling guard pixels reduce chart seams; bilinear filtering still has finite resolution at page edges. This is visual preservation at an explicit sampling density, not pixel identity or exact vector clipping. It projects through the selected objects without visibility/occlusion testing. It does not silently reduce quality.
Output is bounded to 4096 pixels per axis and 16,777,216 total atlas pixels, 500,000 UV corners, and 96 MiB of PNG output. Inputs reuse the ordinary planner limits, plus a 128 MiB RGBA payload and bounded raster ranges. The current path explicitly refuses split per-face palettes, presentation-layer style overrides, and translucent untextured surfaces whose rendering path cannot be preserved.
The page compositor clones directly attached IfcSurfaceStyleRendering metadata
using canonical style precedence. Specular strength, typed roughness/exponent,
reflectance method, transmission/reflection fields, surface name/side and other
supported lighting/refraction leaves survive export. Albedo factors and opacity
already baked into the atlas are neutralized to prevent double multiplication.
Inherited material/representation metadata currently refuses page composition
rather than silently discarding rendering properties. Original shared styles
remain untouched.
Calibrated image annotations¶
appearance::plan_annotation_plane(source, &AnnotationPlaneRequest) creates one
textured IfcAnnotation in an explicitly selected effective IfcSpatialElement
(containerId). It accepts IFC4/IFC4X3, an allocator watermark/revision, distinct
host-generated GlobalId/containmentGlobalId, Name, a registered relative
imageUri, and a generic frame {origin, axisU, axisV, sizeMetres}. The origin is
the image's bottom-left in IFC world Z-up metres; U/V must be orthonormal. It works
for any registered raster source, without decoding or resampling that image.
The plan appends an Annotation/Tessellation representation, textured two-triangle
face set, relative 3D placement and a fresh containment relation. Source entities
and existing containment sets stay untouched. The IfcLite:RegisteredImage
ObjectType identifies this annotation separately from drawing-markup sweep tags.
Container rotation/native units are resolved canonically; ambiguous project/3D
contexts, invalid/cyclic placements, stale/overlapping IDs, duplicate GlobalIds,
nonrigid frames and geometry precision loss refuse the operation.
AnnotationPlanePlan carries the ordinary typed mutation plan, annotationId,
geometryItemId, original frame, rtcOffset and canonical native mesh.
coordinateSpace is ifc-z-up; the mesh retains native snake-case metadata fields.
Positions/normals require the ordinary host Z-up→Y-up conversion exactly once.
Mesh UVs are already top-down for bitmap upload: do not flip them again.
World reconstruction before axis conversion is position + mesh origin + rtcOffset.
The mesh is produced by produce_element_meshes, and normal IFC reparse is the
round-trip oracle. The host atomically registers the source asset, applies the
entities and publishes this new owner through its existing model/history path;
calling the replacement-only appearance preview API cannot create a missing owner.
Source/entity bounds match the appearance planner. Creation reserves 32 IDs,
produces fewer than 32 entities and exactly four vertices/two triangles. Names are
limited to 1024 bytes, revisions to 4096 bytes, and URI validation is shared with
image appearance planning. The method returns no live mutations or partial result
on failure. Its fresh containment relation targets IfcSpatialElement, not only
IfcSpatialStructureElement, matching IFC4/IFC4X3 EXPRESS.
IFC4X3 creation also emits IfcAnnotation.PredefinedType=USERDEFINED and the
optional IfcCartesianPointList3D.TagList slot; IFC4 omits these schema additions.
Scan correspondence registration¶
ifc_lite_processing::appearance::register_scan_correspondences takes a
ScanRegistrationRequest and returns a ScanRegistrationReport. It is the single
native/WASM domain implementation for bounded manual point-pair rigid registration.
Fitting uses only fit; held_out is evaluated afterward, with every residual
retained. The request pins source/destination frame identities and exact asset
hashes; the report binds the frozen request with SHA-256. A report is neither an
alignment mutation nor an accuracy approval.
The solver uses a proper Kabsch rotation with no scale estimation. Non-collinear planar points are valid; coincident/near-collinear point scatter and degenerate correspondence covariance refuse the operation. It reports scatter singular values separately from residuals. Do not confuse point-correspondence rank with rank of plane normals in a point-to-plane experiment. The anchored transform avoids a large standalone translation when evaluating georeferenced points.
The WASM contract documents bounds, coordinate conventions, disjoint observations, request digest and reporting. This opt-in computation never runs during parsing or geometry generation and never mutates source points, IFC placement or renderer alignment state.
Source-bound annotation style references¶
EntityDecoder::styled_item_ids(item_id) returns file-ordered IfcStyledItem
ids attached through Item, or an explicit lookup error. It does not resolve
colour. The lazy inverse index is owned by the shared ColumnarEntityIndex, so
native element decoders and WASM batch decoders reuse the same result or cached
refusal. Standalone decoders retain their own lookup. As with entity offsets,
a shared index must belong to the decoder's exact immutable source bytes.
The lookup refuses sources over 256 MiB, more than one million styled-item edges, records over 16 KiB, more than 64 styled items attached to one geometry item, or malformed source records. Failure never means that an item has no style.
PDF vector graphics-state preparation¶
ifc_lite_processing::pdf_vector::prepare_pdf_vector_page(&PdfVectorPage) returns
a PreparedPdfVectorPage containing the ordered convertible source paths with
complete graphics state snapshots and a FidelityReport (fidelity) listing
every omission kind with counts, page extents and visibility, plus the
exact/raster_only verdict and its binding digest. Canonical Rust validates
bounded decoded input and binds the immutable page/calibration/operations
request. prepare_pdf_vector_page_with_clip accepts an optional rectangle
inside the immutable CropBox; paths wholly outside it are excluded, while any
supported painted envelope crossing it refuses. The original
prepare_pdf_vector_page API remains the whole-page operation. It does not parse
PDF bytes, flatten curves or create IFC entities;
fidelity.exact is deliberately separate from geometric qualification or Apply
readiness. appearance::plan_pdf_fill_annotation_with_clip applies the same
selection when it creates IFC; appearance::plan_pdf_fill_annotation remains
the whole-page API. Both recompute the report and plan a partial page only when
the request's accepted_fidelity_sha256 quotes
it. See the WASM contract
and annotation implementation boundary.
Sequential fixed-grid contour composition¶
ifc_lite_geometry::FixedGridComposition qualifies source contour groups against
one bounded lattice and keeps intermediate booleans in integer coordinates.
new(groups, grid) assigns input IDs by index and an empty-set ID at
groups.len(). overlay(a, b, operation, fill_rule) returns a new local group ID;
vertex_count(id) lets the caller precharge work, and contours(id) exports only
a classified result to model coordinates. IDs belong to their creating context.
boolean_2d_fixed_grid uses this same implementation for a single operation.
The PDF planner charges construction and each stage against its shared work budget. Independent native caps are 257 input groups, 1,024 original vertices, 1,024 vertices per stage, 2,048 retained groups and 16,384 cumulative vertices. Coordinates must be finite within 1e12 model units; quantized coordinates must fit the exact diagnostic range of ±2^50. Original endpoint collapse/uncertain contacts and per-stage unresolved intersections refuse. Shared integer storage avoids introducing a new quantization phase between classification, clipping and paint-order composition.
Symbolic fill provenance¶
extract_symbolic_data_with_provenance(&source) returns an opaque
SymbolicDataWithProvenance. Its data() accessor exposes the unchanged
SymbolicData; fill_items() supplies one optional direct representation-item
ID per fill ordinal. into_parts() consumes both together. Unknown, repeated,
or mapped occurrences have no item identity. The legacy extract_symbolic_data
and publicly constructible primitive/aggregate structs remain compatible.
Serializing the enriched result adds optional geometry_item_id to each known
fill while retaining the existing symbol JSON shape. Deserialization accepts
older symbol JSON and leaves missing provenance unknown. WASM and the server
consume the enriched extraction; callers that only need 2D data can keep using
the legacy API.
Analytic arbitrary-profile topology¶
ifc_lite_geometry::analytic::extract_analytic_profile preserves exact source
line and circular-arc primitives. For IfcArbitraryClosedProfileDef and
IfcArbitraryProfileDefWithVoids, Complete requires a simple outer boundary,
simple inner boundaries inside it, mutually disjoint boundaries, and no nested
inner boundaries. This implements the material-region conditions in
buildingSMART's profile definition.
An invalid or unconfirmed topology returns Unsupported on the source profile,
with no partial loops. extract_analytic_extrusion propagates that status.
Consumers must require Complete before reporting nominal material quantities.
Validation uses outward-rounded analytic curve enclosures and ray crossings; it does not substitute a drawing polygon or reuse the renderer's repair of malformed holes. Adjacent segments may meet at their designated endpoints. Their outgoing derivatives must establish separation, and all possible contacts must stay within the precision neighbourhood of that join. Sub-precision contacts there are treated as the same topological join, consistent with IFC's point-identity tolerance; strict simplicity below that resolution is not claimed. Other segment pairs and separate loops must have certified clearance. The authored segments, winding, area and perimeter are not changed by validation.
The context-free extractor uses
EntityDecoder::geometric_context_precision_range(): a lazy, cached scan of
declared geometric-context Precision values in file-length units. The largest
declaration sets boundary clearance; the smallest declaration limits adjacent
endpoint equivalence. This conservatively covers profiles reused across representations.
Subcontexts inherit from geometric contexts; scanning those base declarations
also covers their precision. This may reject a close boundary valid in a
tighter context when an unrelated context declares a larger precision.
Malformed explicit declarations cause Unsupported. The IFC attribute is
optional and has no specified default; the application uses a minimum clearance
of 1e-9 file units when declarations are absent or smaller, while the join
tolerance is capped at 1e-9 and respects any smaller declaration. This is an
application policy, not an IFC default. Existing loop-closure checks remain
separate; a loose model-wide declaration does not permit larger closure gaps.
Line endpoints and partial-arc chords must be distinguishable at the clearance, and arc radii must exceed it. A conservative area/perimeter resolution screen also rejects thin loops composed entirely of adjacent primitives. These checks may refuse small features that cannot be established at the model's precision. They are not a complete certificate of local feature size: a narrow wedge near an otherwise valid acute corner is not itself classified as a self-intersection.
The validator spends at most 1,000,000 work units per profile across pair
checks, interval refinement and containment, with at most 48 refinement levels
per pair or ray interval. Exhaustion is reported as Unsupported. Numerically
unresolved contacts, overflow and evaluations requiring angles outside the
certified trigonometric domain also return Unsupported. Thus valid but ill-conditioned or
very large profiles may be unavailable for analytic quantities; they are never
silently certified or substituted with an approximation. Rendering is
unaffected. This change makes no performance claim; an end-to-end base/branch
performance verdict is required before merge.