Skip to content

CLI Toolkit

The @ifc-lite/cli package provides a complete BIM toolkit for the terminal. Query, validate, export, create, merge, convert, diff, and script IFC files — no browser or viewer required.

Designed for both humans and LLM terminals (Claude Code, Cursor, Windsurf, etc.).

Installation

npm install -g @ifc-lite/cli

Or run directly with npx:

npx @ifc-lite/cli info model.ifc

Quick Start

# Inspect a model
ifc-lite info model.ifc

# Query walls
ifc-lite query model.ifc --type IfcWall

# Export to CSV
ifc-lite export model.ifc --format csv --type IfcWall --out walls.csv

# Validate against IDS rules
ifc-lite ids model.ifc requirements.ids

# Create an IFC file from scratch
ifc-lite create wall --height 3 --thickness 0.2 --out wall.ifc

# Merge multiple files
ifc-lite merge arch.ifc struct.ifc mep.ifc --out federated.ifc

# Convert schema version
ifc-lite convert model.ifc --schema IFC4 --out model-ifc4.ifc

# Compare two files
ifc-lite diff model-v1.ifc model-v2.ifc

# Validate structure
ifc-lite validate model.ifc

# Detect geometric clashes
ifc-lite clash model.ifc --matrix

# Model KPIs and health check
ifc-lite stats model.ifc

# Ask questions in natural language
ifc-lite ask model.ifc "how many walls?"

# Pull suspect entities into a small standalone IFC
ifc-lite extract-entities model.ifc --product 2O2Fr\$t4X7Zf8NOew3FLKr --out subset.ifc

# Evaluate SDK expressions
ifc-lite eval model.ifc "bim.query().byType('IfcWall').count()"

# Generate lightweight preview artifacts
ifc-lite lod model.ifc --level 0 --out model.lod0.json
ifc-lite lod model.ifc --level 1 --out model.glb --meta model.lod1.json

Global Flags

Available on every command:

Flag Description
--help, -h Show help
--version, -v Show version
--json Machine-readable output
--verbose Show parser and geometry diagnostics on stderr
--quiet Errors only
--debug Verbose plus stack traces on error
--log-level <level> error, warn, info, or debug (explicit level wins over the shorthands)

Commands

view — 3D Viewer

Launch an interactive WebGL 2 viewer in the browser. Control it from the terminal, scripts, or AI assistants via REST API.

ifc-lite view model.ifc                          # Open in browser
ifc-lite view model.ifc --port 3456 --no-open    # Fixed port, no auto-open
ifc-lite view --empty --port 3456                 # Empty scene for live creation

While running, type interactive commands (colorize IfcWall red, isolate IfcSlab, view top, reset) or send commands from another terminal:

ifc-lite view --port 3456 --send '{"action":"colorize","type":"IfcWall","color":[1,0,0,1]}'

The viewer exposes a REST API for external tool integration (/api/command, /api/create, /api/export, /api/status). See the full 3D Viewer & Analysis guide for details.

Flags:

Flag Description
--port <N> Listen on a specific port (default: random)
--no-open Don't auto-open the browser
--empty Start with an empty scene
--send <json> Send a command to an already-running viewer

analyze — Visual Analysis

Query entities and push color overlays to a running viewer. Requires a viewer to be running first.

# Start viewer, then analyze
ifc-lite view model.ifc --port 3456 --no-open &

ifc-lite analyze model.ifc --viewer 3456 --type IfcWall --color red
ifc-lite analyze model.ifc --viewer 3456 --type IfcWall --missing "Pset_WallCommon.FireRating" --color red
ifc-lite analyze model.ifc --viewer 3456 --type IfcSlab --heatmap "Qto_SlabBaseQuantities.GrossArea"
ifc-lite analyze model.ifc --viewer 3456 --type IfcDoor --isolate --color green --flyto
ifc-lite analyze model.ifc --viewer 3456 --rules rules.json --json

Supports property filters (--where), missing-property checks (--missing), heatmaps (--heatmap), and batch rules from a JSON file (--rules). See the full 3D Viewer & Analysis guide.

Flags:

Flag Description
--viewer <port> Port of running viewer (required)
--type <T> IFC type to analyze
--missing <Pset.Prop> Find entities missing a property
--where <expr> Property filter (e.g. GrossArea>100)
--color <name> Color matched entities
--heatmap <Pset.Prop> Gradient color by numeric value
--palette <name> Heatmap palette: blue-red, green-red, rainbow
--isolate Hide non-matching entities
--flyto Fly camera to results
--rules <file> Batch rules from JSON
--json Machine-readable output
--out <file> Write match results as JSON to a file instead of stdout

info — Model Summary

Print schema version, entity counts, storeys, and top entity types.

ifc-lite info model.ifc
ifc-lite info model.ifc --json
  File:     model.ifc
  Schema:   IFC4
  Size:     12.3 MB
  Entities: 45,821
  Parsed:   340ms

  Storeys:
    - Ground Floor
    - First Floor
    - Second Floor

  Entity types (top 10):
     Type              │ Count
    ───────────────────┼───────
     IfcWall           │ 234
     IfcDoor           │ 87
     IfcWindow         │ 156
     ...
{
  "file": "model.ifc",
  "schema": "IFC4",
  "fileSize": 12902400,
  "entityCount": 45821,
  "parseTime": "340ms",
  "storeys": ["Ground Floor", "First Floor", "Second Floor"],
  "typeCounts": {
    "IfcWall": 234,
    "IfcDoor": 87,
    "IfcWindow": 156
  }
}

stats - Model KPIs

Auto-calculated building metrics and a model health check in one command.

ifc-lite stats model.ifc
ifc-lite stats model.ifc --json

Flags:

Flag Description
--json Full report as JSON

query — Query Entities

Filter entities by type, properties, or spatial structure. Optionally include properties, quantities, materials, classifications, attributes, relationships, type properties, and documents.

# By type
ifc-lite query model.ifc --type IfcWall
ifc-lite query model.ifc --type IfcWall,IfcDoor

# With property filter
ifc-lite query model.ifc --type IfcWall --where "Pset_WallCommon.IsExternal=true"

# With properties and quantities included
ifc-lite query model.ifc --type IfcWall --props --quantities --json

# With materials, classifications, and relationships
ifc-lite query model.ifc --type IfcWall --materials --classifications --relationships --json

# All data at once
ifc-lite query model.ifc --type IfcWall --all --json

# Count only
ifc-lite query model.ifc --type IfcDoor --count

# Aggregate quantities
ifc-lite query model.ifc --type IfcWall --sum GrossSideArea
ifc-lite query model.ifc --type IfcWall --group-by material --json

# Discover which quantity/property names exist on a type
ifc-lite query model.ifc --type IfcWall --quantity-names
ifc-lite query model.ifc --type IfcWall --property-names

# Spatial tree
ifc-lite query model.ifc --spatial
ifc-lite query model.ifc --spatial --summary

# Pagination
ifc-lite query model.ifc --type IfcWall --limit 10 --offset 20

Flags:

Flag Description
--type <T> Filter by IFC type (comma-separated)
--where <filter> Property filter: PsetName.PropName=Value
--select <selector> IfcOpenShell-style selector (classes union with --type; properties AND with --where)
--storey <name> Filter to elements in a storey
--props Include property sets in output
--quantities Include quantity sets in output
--materials Include material assignments
--classifications Include classification references
--attributes Include IFC schema attributes
--relationships Include relationship data
--type-props Include type-level properties
--documents Include linked documents
--all Include all data (properties, quantities, materials, etc.)
--count Return count instead of entities
--sum / --avg / --min / --max <Qty> Aggregate a quantity across matches
--group-by <key> Group results (e.g. material)
--unique <prop> List distinct values of a property
--quantity-names List quantity names present on a type (requires --type)
--property-names List property names present on a type (requires --type)
--sort <key> / --desc Sort results
--spatial Show spatial tree (storeys and elements)
--summary Condensed spatial tree (with --spatial)
--limit <N> Limit result count
--offset <N> Skip first N results
--json JSON output

--select accepts selector text directly — a lossless subset of the IfcOpenShell grammar; unsupported constructs throw. --type and --where remain the CLI's own filter surface for everything else. See Selector Syntax for how each selector construct is spelled across surfaces.


props — Entity Properties

Show all properties, quantities, materials, classifications, and relationships for a single entity.

ifc-lite props model.ifc --id 42

Returns a complete JSON object with:

  • attributes — IFC schema attributes (Name, Description, ObjectType, etc.)
  • properties — All IfcPropertySet data
  • quantities — All IfcElementQuantity data
  • classifications — Classification references
  • materials — Material assignments (layers, profiles, constituents)
  • typeProperties — Properties from the entity's type object
  • relationships — Voids, fills, groups, connections

export — Export Data

Export entity data to CSV, JSON, IFC STEP, and other formats.

# CSV export
ifc-lite export model.ifc --format csv --type IfcWall --columns Name,Type,GlobalId

# JSON export
ifc-lite export model.ifc --format json --type IfcWall,IfcDoor

# With property columns (dot notation)
ifc-lite export model.ifc --format csv --type IfcWall \
  --columns Name,Type,Pset_WallCommon.IsExternal,Pset_WallCommon.FireRating

# IFC STEP re-export with schema conversion
ifc-lite export model.ifc --format ifc --schema IFC4 --out filtered.ifc

# OpenUSD (.usda) — a real Z-up USD stage (whole model; opens in usdview/Blender/Omniverse)
ifc-lite export model.ifc --format usd --out model.usda

# Limit results
ifc-lite export model.ifc --format csv --type IfcWall --limit 50

# Write to file
ifc-lite export model.ifc --format csv --out walls.csv

Flags:

Flag Description
--format <fmt> csv, json, ifc, obj, gltf, glb, jsonld, step, ifcx, usd, hbjson, or dfjson
--type <T> Filter entities by type
--where <filter> Property filter: PsetName.PropName=Value
--storey <name> Filter to elements in a storey
--columns <cols> Comma-separated columns (supports PsetName.PropName)
--separator <sep> CSV separator (default: ,)
--schema <ver> IFC schema for STEP export (IFC2X3, IFC4, IFC4X3)
--name <str> Model name for geometry exports
--limit <N> Limit result count
--diagnostics Print a geometry summary after mesh-based exports
--out <file> Write to file instead of stdout

schedule — Tabular Schedules

Generate an AEC-style schedule (door schedule, window schedule, room schedule, material takeoff, …) for one IFC class: a filtered, columnar table with attribute/property/quantity columns, optional sort/group/subtotal rows, and CSV/JSON/Markdown/HTML output. Column values resolve through the same property/quantity resolver export uses and --where reuses query's exact filter, so a schedule always agrees with the equivalent query/export invocation.

# Explicit columns, a property filter, JSON output
ifc-lite schedule tests/models/ara3d/AC20-FZK-Haus.ifc --type IfcDoor \
  --columns "Name, Mark=Tag, Width=Qto_DoorBaseQuantities.Width" \
  --where "Pset_DoorCommon.IsExternal=true" --format json

# A built-in preset — sensible columns with no other flags
ifc-lite schedule tests/models/ara3d/AC20-FZK-Haus.ifc --preset door

# Save the resolved definition, then reload it later with --spec
ifc-lite schedule tests/models/ara3d/AC20-FZK-Haus.ifc --preset door --save door-schedule.json
ifc-lite schedule tests/models/ara3d/AC20-FZK-Haus.ifc --spec door-schedule.json --format md

Flags:

Flag Description
--type <T> IFC class to schedule (e.g. IfcDoor); auto-prefixes Ifc like query/export
--columns <spec> Comma-separated Header=path pairs; a bare path is its own header. path is an attribute name (Name, Tag, …), PsetName.PropName, or QtoName.QtyName
--where <filter> Property filter: PsetName.PropName=Value (same resolver as query --where)
--sort <spec> "Header[:asc\|desc], ..." — stable multi-key sort by column header; numeric when both cells parse as numbers, else string; missing values sort last
--group-by <headers> "Header, ..." — orders rows so each group is contiguous (group key ascending, or --sort's direction when the group header is also a sort key)
--subtotals <spec> "count \| sum:Header \| avg:Header \| min:Header \| max:Header, ..." — a subtotal row after each group plus a grand total (grand total only, without --group-by)
--preset <name> door \| window \| space \| wall \| material-takeoff — default --type/--columns (and a default sort/group for space/material-takeoff); an explicit flag overrides the preset's corresponding default
--format <fmt> csv (default), json, md, or html
--spec <file.json> Load a reusable schedule definition (type/columns/where/sort/groupBy/subtotals/format, plus an optional preset to start from); beats a --preset default, but an explicit flag still beats both
--save <file.json> Write the schedule definition this invocation resolved to (after any --preset/--spec defaults are folded in), so it's self-contained and reloadable with --spec alone

A missing value is an empty CSV/Markdown cell or a JSON null. CSV/Markdown/HTML cells are escaped for their format (RFC-4180 CSV escaping with a formula-injection guard, |/backslash/newline escaping for Markdown, full HTML-entity escaping for HTML), since model text is untrusted.


diagnose-geometry - Geometry Diagnostics

Run geometry extraction headlessly and report CSG / opening diagnostics: opening classification, per-reason failure breakdown, fast-path engagement, and the worst-failing host elements. This is the same diagnostics contract the viewer and server surface.

ifc-lite diagnose-geometry model.ifc
ifc-lite diagnose-geometry model.ifc --json
ifc-lite diagnose-geometry model.ifc --type IfcWall
ifc-lite diagnose-geometry model.ifc --product '0YvCT2_$X3_xJG3rzD8L_8'

Flags:

Flag Description
--product <id\|GUID> Narrow the worst-hosts detail to one product (express ID or GlobalId)
--type <T> Narrow the worst-hosts detail to one IFC type
--out <file> Write the report to a file
--json Raw diagnostics object as JSON

Aggregate counts always describe the whole file; --product / --type only narrow which per-product rows are shown.


extract-entities - Isolate Entities

Pull selected entities out of a large IFC into a small, valid, viewable standalone model. Useful for reproducing a suspect element in isolation.

# By GlobalId or express ID (repeatable, or comma-separated)
ifc-lite extract-entities model.ifc --product '2O2Fr$t4X7Zf8NOew3FLKr' --out subset.ifc

# By type or storey
ifc-lite extract-entities model.ifc --type IfcWall --out walls.ifc
ifc-lite extract-entities model.ifc --storey "Level 2" --out level2.ifc

# Auto-triage: extract the N most unusual meshes
ifc-lite extract-entities model.ifc --detect --top 10 --out suspects.ifc

# Triage report only, no extraction
ifc-lite extract-entities model.ifc --detect --report --json

# Extract and open the result in the 3D viewer
ifc-lite extract-entities model.ifc --type IfcStair --out stairs.ifc --view

Selectors are unioned. The output carries each selected product's full forward reference closure plus the shared context roots (IfcProject, units, geometric contexts, and the backward closure of the selection's spatial-ancestor chain — only the spatial structures the selection actually sits under, not every spatial-structure instance in the model), spatial-containment relations, and each kept host's openings and fillers (IfcRelVoidsElement / IfcRelFillsElement), so the subset parses and renders on its own.

Flags:

Flag Description
--product <id\|GUID> Select specific products (repeatable or comma-separated)
--type <T> Select every product of a type
--storey <GUID\|name\|id> Select every product placed under a storey
--detect Select the meshes a geometry-triage pass ranks most unusual
--top <N> How many triage hits to keep (default 20, with --detect)
--report Print the triage report without extracting (with --detect)
--out <file> Output IFC file
--view Open the extracted subset in the 3D viewer
--port <N> Viewer port (with --view)
--json Machine-readable output

anonymize - Anonymized Isolated Export

Pick a seed selection, expand it by relationship context (host walls, openings/fillers, type objects, materials, aggregate parents/children, the spatial containment chain up to IfcProject, and optionally structurally connected neighbours), then export exactly that subset as a STEP file with every project-identifying signal removed — while the geometry-relevant local transformations (rotations in the placement chain, non-orthogonal cuts) survive, so a parsing bug keeps reproducing without the source model ever leaving your machine. Built on the same collectRelatedEntities / exportAnonymizedSubset functions @ifc-lite/export exposes to a TypeScript caller (see Exporting).

# By type — every IfcWindow plus its host wall, opening, storey/building/site chain
ifc-lite anonymize model.ifc --type IfcWindow --out anon.ifc

# By express ID or GlobalId (repeatable, or comma-separated)
ifc-lite anonymize model.ifc --id 42,108 --out anon.ifc
ifc-lite anonymize model.ifc --guid '2O2Fr$t4X7Zf8NOew3FLKr' --out anon.ifc

# By storey
ifc-lite anonymize model.ifc --storey "Level 2" --out anon.ifc

# Narrow or widen the relationship context, and save the old->new GlobalId map
ifc-lite anonymize model.ifc --type IfcWindow --no-rel-associates-material --connect-depth 1 \
  --out anon.ifc --guid-map anon.guidmap.json --json

Selectors are unioned, and every one of them fails loudly on zero matches (never a silent empty export). Names, property sets, and currency are maximally-scrubbed by default and can be dialed back with a --keep-* flag. GlobalId regeneration, owner-history scrubbing, georeferencing/address removal, and root-placement zeroing are unconditional — there is no flag to keep any of those as authored. See AnonymizeOptions in @ifc-lite/export for the exact defaults. The GUID map links back to the original, identifying model: --guid-map writes it to a separate file, never into the exported .ifc itself, and it should not be shared alongside the export.

Flags:

Flag Description
--id <N,...> Select specific entities by express ID (repeatable or comma-separated)
--guid <G,...> Select specific entities by GlobalId (repeatable or comma-separated)
--type <T> Select every entity of a type
--storey <GUID\|name\|id> Select every entity contained in a storey
--keep-psets Keep property sets on the selection instead of dropping them
--keep-names Keep IfcRoot Name/LongName/Description/Tag as authored instead of pseudonymizing them
--keep-other-names Keep ObjectType, IfcProject.Phase, and non-IfcRoot names (materials, surface styles, layers, profiles) as authored
--keep-currency Keep IfcMonetaryUnit.Currency as authored instead of rewriting it to USD
--no-rel-voids-element Don't expand to a selected opening's host element (IfcRelVoidsElement)
--no-rel-fills-element Don't expand the filler<->opening<->host chain (IfcRelFillsElement)
--no-rel-defines-by-type Don't include a selected object's IfcTypeObject (IfcRelDefinesByType)
--no-rel-associates-material Don't include a selected object's material assignment (IfcRelAssociatesMaterial)
--no-rel-aggregates Don't walk IfcRelAggregates parents/children
--no-rel-nests Don't walk IfcRelNests parents/children
--connect-depth <N> BFS depth for IfcRelConnectsPathElements neighbours (default 0)
--guid-map <file> Write the old->new GlobalId mapping to a separate JSON file
--out <file> Output IFC file (required)
--json Machine-readable summary (counts, warnings, pruned/zeroed entities)

The spatial containment chain (storey -> building -> site -> IfcProject) is always included and has no disabling flag — a file with no project is not a valid reproduction of anything.


lod — Lightweight LOD Artifacts

Generate lightweight geometry artifacts for previews, offline packaging, and degraded delivery flows.

# LOD0 JSON envelopes
ifc-lite lod model.ifc --level 0 --out model.lod0.json

# LOD1 GLB + metadata
ifc-lite lod model.ifc --level 1 --out model.glb --meta model.lod1.json

# Machine-readable summary
ifc-lite lod model.ifc --level 1 --out model.glb --json

LOD0 produces JSON with: - world-space bounding boxes - transforms - centroids - IFC class and identity metadata

LOD1 produces: - a GLB geometry file - a metadata JSON file with generation status and expressId mapping

If meshing fails, LOD1 falls back to box geometry derived from LOD0.

Flags:

Flag Description
--level <N> 0 for JSON envelopes, 1 for GLB geometry
--out <file> Output file (required for LOD1)
--meta <file> Metadata file for LOD1 (default: derived from --out)
--json Machine-readable summary to stdout

ids — IDS Validation

Validate an IFC file against IDS (Information Delivery Specification) rules.

ifc-lite ids model.ifc requirements.ids
ifc-lite ids model.ifc requirements.ids --json
ifc-lite ids model.ifc requirements.ids --locale de

Returns pass/fail summary with exit code 0 (pass) or 1 (fail).

Flags:

Flag Description
--json Full validation report as JSON
--locale <lang> Message language: en, de, fr

bcf — BCF Collaboration

Create, read, and manage BCF (BIM Collaboration Format) files.

# Create a new BCF topic
ifc-lite bcf create --title "Missing fire door" --description "Level 2, Room 201" --out topic.bcf

# List topics in a BCF file
ifc-lite bcf list topics.bcf

# Add a comment to a BCF file
ifc-lite bcf add-comment --file topics.bcf --text "Fixed in revision 3" --out updated.bcf

clash - Clash Detection

Detect geometric clashes between elements. Meshes the model headlessly, then runs the clash engine with either a single ad-hoc rule (--a / --b) or the standard discipline matrix (--matrix). Results can be exported as a BCF archive or as a flat CSV table for spreadsheets and BI tools.

# Standard discipline matrix
ifc-lite clash model.ifc --matrix
ifc-lite clash model.ifc --matrix --json

# Ad-hoc rule: ducts/pipes vs walls, 5 cm clearance
ifc-lite clash model.ifc --a "IfcDuct*|IfcPipe*" --b "IfcWall*" --mode clearance --clearance 0.05

# Export clashes as BCF topics
ifc-lite clash model.ifc --matrix --bcf clashes.bcfzip

# Export every clash as one CSV row (GlobalIds when the elements have them, plus types, names, storey, distance, review) for Excel / Power BI
ifc-lite clash model.ifc --matrix --csv clashes.csv

Flags:

Flag Description
--matrix Run the standard discipline matrix rules
--a <pattern> Type pattern for set A (glob, \|-separated; default *)
--b <pattern> Type pattern for set B
--mode <m> hard (default) or clearance
--tolerance <m> Penetration tolerance in metres (hard mode)
--clearance <m> Required clearance in metres (clearance mode)
--bcf <file> Write results as a BCF archive. Viewpoint cameras are in the model's IFC world coordinates, so other BCF tools frame the clash on georeferenced models too
--group <g> BCF topic grouping: cluster (default), rule, typePair, element
--bcf-status <s> Topic status for exported BCF topics
--max-topics <N> Cap the number of BCF topics
--csv <file> Write every clash as one row of an RFC 4180 CSV table: ClashId, Rule, Status, Severity, Review, ReviewComment, ReviewUpdatedAt, GlobalIdA, GlobalIdB, KeyA, KeyB, ModelA, ModelB, TypeA, TypeB, NameA, NameB, StoreyA, StoreyB, PointX, PointY, PointZ, Distance, DistanceKind, Group. Uncapped (the --json limit is a display cap); GlobalId* is empty for an element without an IfcGUID while Key* always carries the run's durable key
--json JSON output (stdout carries exactly one JSON document; progress and geometry diagnostics go to stderr)

create — Create IFC Files

Generate IFC building elements from CLI flags or JSON input. Supports 29 element types (28 elements plus storey, which emits a bare project skeleton) with property sets, quantities, materials, and colors.

Coordinates are storey-relative. --position, --start, and --end are passed through unchanged to @ifc-lite/create, and every element is placed against the storey created by --storey / --elevation. The storey placement is what applies --elevation, exactly once — so an element standing on the floor of a storey created with --elevation 3 takes Z = 0, not Z = 3.

Changed in @ifc-lite/create 2.0.0. 21 of the 28 element types previously read these flags as world coordinates while the other 7 were already storey-relative, so a nonzero --elevation put them on two different datums. They are now uniformly storey-relative. If you were compensating by adding the elevation into --position / --start / --end yourself, drop it — otherwise the element lands at twice the elevation. Nothing changes when --elevation is 0 or omitted (the default).

# Basic elements
ifc-lite create wall --start 0,0,0 --end 5,0,0 --height 3 --thickness 0.2 --out wall.ifc
ifc-lite create slab --width 10 --depth 8 --thickness 0.3 --out slab.ifc
ifc-lite create column --position 0,0,0 --height 3 --width 0.3 --depth 0.3 --out column.ifc
ifc-lite create beam --start 0,0,3 --end 5,0,3 --width 0.2 --height 0.4 --out beam.ifc

# Stairs, roofs, doors, windows
ifc-lite create stair --number-of-risers 12 --riser-height 0.175 --tread-length 0.28 --width 1.2 --out stair.ifc
ifc-lite create roof --width 10 --depth 8 --thickness 0.25 --position 0,0,3 --out roof.ifc
ifc-lite create gable-roof --width 10 --depth 8 --slope 0.5 --thickness 0.25 --out gable.ifc
ifc-lite create door --width 0.9 --height 2.1 --position 0,0,0 --out door.ifc
ifc-lite create window --width 1.2 --height 1.5 --position 0,0,1 --out window.ifc

# Structural elements
ifc-lite create footing --width 2 --depth 2 --height 0.5 --predefined-type PAD_FOOTING --out footing.ifc
ifc-lite create pile --length 10 --diameter 0.6 --position 0,0,0 --out pile.ifc
ifc-lite create ramp --width 1.5 --length 5 --thickness 0.2 --rise 0.5 --out ramp.ifc
ifc-lite create railing --start 0,0,0 --end 5,0,0 --height 1.0 --out railing.ifc
ifc-lite create member --start 0,0,0 --end 3,0,3 --width 0.1 --height 0.1 --out brace.ifc

# Special elements
ifc-lite create space --width 5 --depth 4 --height 3 --long-name "Living Room" --out room.ifc
ifc-lite create curtain-wall --start 0,0,0 --end 10,0,0 --height 3 --out curtain.ifc
ifc-lite create furnishing --width 1 --depth 0.6 --height 0.8 --name "Desk" --out desk.ifc
ifc-lite create proxy --width 1 --depth 1 --height 1 --name "Unknown Element" --out proxy.ifc
ifc-lite create plate --width 2 --depth 1 --thickness 0.01 --out plate.ifc

# Advanced profiles
ifc-lite create circular-column --radius 0.15 --height 3 --out col.ifc
ifc-lite create hollow-circular-column --radius 0.3 --wall-thickness 0.02 --height 3 --out hcol.ifc
ifc-lite create i-shape-beam --overall-width 0.2 --overall-depth 0.4 --web-thickness 0.01 --flange-thickness 0.015 --out ib.ifc
ifc-lite create l-shape-member --depth 0.1 --width 0.1 --thickness 0.01 --out lm.ifc
ifc-lite create t-shape-member --flange-width 0.15 --depth 0.15 --web-thickness 0.008 --out tm.ifc
ifc-lite create u-shape-member --depth 0.15 --flange-width 0.08 --web-thickness 0.008 --out um.ifc
ifc-lite create rectangle-hollow-beam --xdim 0.1 --ydim 0.2 --wall-thickness 0.005 --out rhb.ifc

# With property sets, materials, and colors
ifc-lite create wall --out w.ifc \
  --pset '{"Name":"Pset_WallCommon","Properties":[{"Name":"IsExternal","NominalValue":true}]}'
ifc-lite create wall --out w.ifc \
  --material '{"Name":"Concrete","Category":"Structural"}'
ifc-lite create wall --out w.ifc --color 0.8,0.2,0.2

# From JSON (pipe-friendly)
echo '{"Start":[0,0,0],"End":[10,0,0],"Height":3,"Thickness":0.2}' \
  | ifc-lite create wall --from-json --out wall.ifc

Supported element types:

Category Types
Walls wall, curtain-wall
Floors/Roofs slab, roof, gable-roof
Columns column, circular-column, hollow-circular-column
Beams beam, i-shape-beam, rectangle-hollow-beam
Members member, l-shape-member, t-shape-member, u-shape-member
Openings door, window, wall-door, wall-window
Circulation stair, ramp, railing
Foundation footing, pile
Other space, plate, furnishing, proxy

Common Flags:

Flag Description
--start <x,y,z> Start point (walls, beams, railings)
--end <x,y,z> End point (walls, beams, railings)
--position <x,y,z> Position (columns, doors, slabs, etc.)
--height <N> Element height
--width <N> Element width
--depth <N> Element depth
--thickness <N> Element thickness
--name <str> Element name
--project <str> Project name
--storey <str> Storey name
--elevation <N> Storey elevation
--pset <json> Add property set (JSON)
--qset <json> Add element quantity (JSON)
--material <json> Add material (JSON)
--color <r,g,b> Set color (0-1 per channel)
--from-json Read parameters from stdin JSON
--out <file> Output IFC file (required)
--json Output creation stats as JSON

mutate - Modify Properties

Modify properties or attributes of IFC entities and save the result as a new file.

# Set a property on one entity
ifc-lite mutate model.ifc --id 42 --set Pset_WallCommon.IsExternal=true --out out.ifc

# Set an attribute (no dot = attribute)
ifc-lite mutate model.ifc --id 42 --set Name=TestWall --out out.ifc

# Bulk: all walls matching a filter
ifc-lite mutate model.ifc --type IfcWall --where "Pset_WallCommon.IsExternal=true" \
  --set Pset_WallCommon.FireRating=REI60 --out out.ifc

Flags:

Flag Description
--id <N> Target a single entity by express ID
--type <T> Target all entities of a type
--where <filter> Narrow --type targets: Pset.Prop<op>Value (=, !=, >, <, >=, <=, contains)
--set <P=V> Property (Pset.Prop=Value) or attribute (Name=Value) to set; repeatable (required)
--out <file> Output file (required)
--json Mutation stats as JSON

generate-spaces - Derive IfcSpace

Derive IfcSpace volumes from the building's walls (room footprints), using storey datums for floor-to-floor height, and write the augmented IFC.

ifc-lite generate-spaces model.ifc --out with-spaces.ifc
ifc-lite generate-spaces model.ifc --storey "Level 1" --out l1.ifc
ifc-lite generate-spaces model.ifc --dry-run --json
ifc-lite generate-spaces model.ifc --list-storeys

Flags:

Flag Description
--out <file> Output IFC with the new IfcSpace entities (omit with --dry-run)
--storey <id\|name\|all> Storey express ID, name substring, or all (default)
--snap <m\|auto> Corner-closing tolerance in metres, or auto (default)
--height <m\|auto> Space height in metres, or auto = floor-to-floor (default)
--top-height <m> Height for the topmost storey under auto (default 3)
--min-area <m2> Drop regions below this area (default 0.5)
--name-pattern <p> Name template; {n} = index, {storey} = storey name
--predefined-type <t> IfcSpacePredefinedType (default INTERNAL)
--boundary <mode> Space boundary vs walls: center, inner (default), outer
--divider-type <t> Extra element type to treat as a wall divider (repeatable)
--dry-run Detect and report only; write nothing
--force Re-derive even if the model already has generated spaces (may duplicate)
--list-storeys List storeys (ID, name, elevation) and exit
--json Machine-readable output

merge — Merge IFC Files

Combine multiple IFC files into a single federated model.

# Merge two files
ifc-lite merge arch.ifc struct.ifc --out federated.ifc

# Merge multiple files with schema conversion
ifc-lite merge file1.ifc file2.ifc file3.ifc --schema IFC4 --out merged.ifc

# JSON output with stats
ifc-lite merge a.ifc b.ifc --out merged.ifc --json

# Mixed units: rescale every model into the first file's unit (one single-unit project)
ifc-lite merge metric.ifc imperial.ifc --unit-reconciliation normalize --out merged.ifc

The merger unifies spatial hierarchy (sites, buildings, storeys) by name and elevation, and offsets entity IDs to avoid collisions. Models that share the first file's length unit merge into a single IfcProject; a model with a different unit is federated (kept as its own project) unless --unit-reconciliation normalize rescales it into the first file's unit. The per-container matching strategy can be pinned down with --merge-sites / --merge-buildings / --merge-storeys (see Spatial matching strategy).

Flags:

Flag Description
--schema <ver> Target schema (IFC2X3, IFC4, IFC4X3)
--unit-reconciliation <mode> Mixed-unit handling: auto (default, federate differing units), normalize (rescale into the first file's unit → one single-unit project), assume-shared (force one project without rescaling)
--merge-sites <mode> IfcSite matching across models: single (unify iff each model has exactly one site, Name ignored) or by-name (Name match only, no single-instance fallback). Omitted: Name match, else single-instance fallback
--merge-buildings <mode> Same modes as --merge-sites, applied to IfcBuilding
--merge-storeys <mode> IfcBuildingStorey matching: by-name, by-elevation, or by-name-then-elevation (default)
--drop-empty-containers Leave out spatial containers (site, building, storey, space) the merge finds holding nothing — the "Merge Projects" recipe step matching alone does not cover. Off by default
--out <file> Output file (required)
--json Output merge stats as JSON

convert — Schema Conversion

Convert an IFC file between schema versions.

ifc-lite convert model.ifc --schema IFC4 --out model-ifc4.ifc
ifc-lite convert old-model.ifc --schema IFC4X3 --out modern.ifc
ifc-lite convert model.ifc --schema IFC2X3 --out legacy.ifc --json

Handles entity type mapping automatically (e.g., IfcWallStandardCase → IfcWall when upgrading from IFC2X3 to IFC4). An IFC4X3 target is declared as FILE_SCHEMA(('IFC4X3_ADD2')), the ISO 16739-1:2024 identifier for the layouts ifc-lite writes (see which schema identifier is written).

Flags:

Flag Description
--schema <ver> Target schema: IFC2X3, IFC4, IFC4X3, IFC5 (required)
--out <file> Output file (required)
--json Output conversion stats as JSON

diff — Compare IFC Files

Compare two IFC files and report differences.

# Type-level comparison
ifc-lite diff model-v1.ifc model-v2.ifc

# With entity-level comparison by GlobalId
ifc-lite diff model-v1.ifc model-v2.ifc --by-entity

# JSON output
ifc-lite diff model-v1.ifc model-v2.ifc --json

Reports:

  • Entity count differences
  • Type-level additions/removals
  • GlobalId-based entity tracking (with --by-entity)

Flags:

Flag Description
--by-entity Compare every IfcObjectDefinition by GlobalId (added / removed / common)
--by-content Per-entity comparison via the @ifc-lite/diff engine, pairing re-GUIDed elements by content
--geometry Run the wasm mesh pass and attach world geometry hashes/boxes/volumes (implies --by-content; falls back to data-only with a warning if the wasm runtime isn't built)
--split-merge Opt into the split/merge detector (implies --by-content; needs --geometry to produce claims)
--successors Opt into the successor-match detector (implies --by-content; needs --geometry to produce claims)
--identity-out <file> Write the accepted matches to an identity-map sidecar (implies --by-content)
--identity-in <file> Replay a sidecar's claims so those elements are matched by key (implies --by-content)
--key-from <Tag\|Pset.Prop> Key the comparison on an authored identifier instead of GlobalId (implies --by-content)
--lineage-out <file> Write the lineage this comparison establishes, for rekey (implies --by-content)
--lineage-in <file> Replay a lineage's one-to-one entries and carry the rest forward (implies --by-content)
--accept <map.json> Fold reviewed claims into lineage: successor:* reasons become replaced; other accepted identities become identity
--json JSON output

Both comparison modes cover the same entities: every IfcObjectDefinition in the file. See what gets compared below.

Re-GUIDed models: --by-content and the identity map

A model re-exported from scratch by another tool gets entirely new GlobalIds, so the comparison above reports the whole file as deleted-and-added. --by-content runs the real diff engine with content-keyed matching instead, and can write the matches it accepted into a sidecar you review once and replay afterwards:

# Run 1: recognise the re-GUIDed elements and write the claims down.
ifc-lite diff model-v1.ifc model-v2.ifc --by-content --identity-out renames.json

# Review renames.json, then replay it — those elements are now matched by key.
ifc-lite diff model-v1.ifc model-v2.ifc --identity-in renames.json

The sidecar pins the SHA-256 of both files, and --identity-in refuses a map that was verified against a different pair. Nothing in the files is ever rewritten: an identity map is a reviewable claim alongside the models, not an edit to them. Without --geometry this path compares data only, so every unambiguous match reports as renamed; add --geometry (and --split-merge / --successors) to attach world geometry and tell moved/reshaped apart, and to produce split/merge and successor claims. See Model Diff for the full semantics.

--identity-out and --lineage-out refuse to write over either input model.

Authored keys and lineage

When the model maintains an identifier on purpose — an asset code in a property set, a stable Tag — --key-from keys the comparison on it, and a redrawn element keeps its key. --lineage-out writes the one-to-many record rekey consumes when an element was split or merged:

ifc-lite diff model-v1.ifc model-v2.ifc --key-from Pset_Asset.AssetId --lineage-out lineage.json

See Stable Element Identity for the workflow end to end.

What gets compared

Both --by-entity and --by-content compare every IfcObjectDefinition in the file — every IfcObject (products, but also tasks, actors, controls, resources and groups), plus IfcTypeObject and IfcProject. The other two IfcRoot branches are left out on purpose: an IfcRelationship is identified by its endpoints rather than in its own right, and a property set's contents are already part of its owner's comparison, so including it would report every edited property twice.

Membership comes from the schema inheritance chain, so an entity that is not an IfcRoot at all — a material, a surface style, a classification, a projected CRS — is not compared under its name, and a schedule task or actor is compared even though it carries no geometry. The chain is read from every schema ifc-lite bundles (IFC2X3, IFC4 and IFC4X3), so a class that only one of them declares — IfcMove and IfcSpaceProgram in IFC2X3, IfcRoad and IfcAlignment in IFC4X3 — is classified as what it is, not as an unknown. One consequence is deliberate: a vendor-specific IfcRoot subtype that no IFC schema declares is not compared unless its class name ends in Type, because there is no chain to prove it is an object rather than a resource.


rekey — Carry a Table Across a Revision

Rewrite a CSV or JSON table keyed on one revision's element keys to the next revision, through a lineage diff --lineage-out (or the viewer) wrote.

# Split rows are copied to every piece; rows with nowhere to go are set aside.
ifc-lite rekey costs.csv --lineage lineage.json --key-column GlobalId --out costs-v2.csv --orphans orphans.csv

# Follow the largest volume share instead, on a JSON array of objects.
ifc-lite rekey rows.json --lineage lineage.json --key-column id --policy largest-share --out rows-v2.json

Flags:

Flag Description
--lineage <file> The lineage sidecar (required)
--out <file> Where to write the rekeyed table (required)
--key-column <name> The column holding the old key (default GlobalId)
--policy <p> copy-to-all (default), largest-share, or orphan-on-split
--orphans <file> Where rows with nowhere to go are written (default <out>.orphans.<ext> when there are any)
--json JSON summary

A row whose key the lineage does not mention keeps its key unless the lineage's deleted list names it. The output gains lineage_relation and lineage_from columns. Orphans are never dropped. See Stable Element Identity.


validate — Structural Validation

Check an IFC file for structural issues.

ifc-lite validate model.ifc
ifc-lite validate model.ifc --json

Checks:

  • Required entities (IfcProject, IfcSite, IfcBuilding)
  • Single IfcProject presence
  • Building storeys existence
  • GlobalId uniqueness — across every IfcRoot subtype the file holds, read from the inheritance chain of every schema ifc-lite bundles (IFC2X3, IFC4 and IFC4X3). Classes only one schema declares (IfcScheduleTimeControl, IfcSpaceProgram, IfcServiceLife in IFC2X3; IfcCourse, IfcBorehole in IFC4X3) are checked like any other. Entities that are not IfcRoot subtypes — materials, surface styles, classifications — are deliberately left out, because they carry a name where an IfcRoot carries a GlobalId and two same-named materials are not a duplicate
  • Named elements
  • Reference integrity (every #N attribute reference must point at an entity that exists in the file; each dangling reference is reported with the referencing entity, attribute slot, and missing target — detailed up to the first 50, after which a single rollup issue reports the count of remaining dangling references)

Returns exit code 0 (valid) or 1 (errors found).

Flags:

Flag Description
--json Full report as JSON

bsdd — buildingSMART Data Dictionary

Query the bSDD API for IFC class information, property sets, and search.

# Get class info
ifc-lite bsdd class IfcWall

# Search for classes
ifc-lite bsdd search "concrete wall"

# List standard property sets
ifc-lite bsdd psets IfcWall

# List standard quantity sets
ifc-lite bsdd qsets IfcSlab

Subcommands:

Subcommand Description
class <IfcType> Get class info (definition, related types, properties)
search <query> Search bSDD for classes by keyword
psets <IfcType> List standard property sets and their properties
qsets <IfcType> List standard quantity sets

ext — Extension Toolkit

Author, validate, sign, and run tests against IFClite extensions. The ext subcommands are the hand-authoring side of the Extensions feature.

# Scaffold a starter bundle
ifc-lite ext init my-tool
ifc-lite ext init my-tool --id com.example.my-tool --name "My Tool"

# Validate a bundle directory or manifest.json
ifc-lite ext validate ./my-tool
ifc-lite ext validate ./my-tool --json

# Pack a directory into a .iflx
ifc-lite ext pack ./my-tool --out my-tool.iflx

# Run manifest.tests against a bundle
ifc-lite ext test ./my-tool
ifc-lite ext test ./my-tool --bail --json

# Generate an Ed25519 keypair for signing
ifc-lite ext keygen --out ~/.config/ifclite/key --label "Alice"

# Sign a bundle (or pack + sign in one step)
ifc-lite ext sign ./my-tool --key ~/.config/ifclite/key.private.iflk --out my-tool.iflx
ifc-lite ext pack ./my-tool --sign --key ~/.config/ifclite/key.private.iflk --out my-tool.iflx

# Verify a .iflx (with optional public-key fingerprint check)
ifc-lite ext verify my-tool.iflx
ifc-lite ext verify my-tool.iflx --key ~/.config/ifclite/key.public.iflk --json

Subcommands:

Subcommand Purpose
init <dir> Scaffold a minimal valid bundle (manifest, README, one command).
validate <path> Validate a manifest or a bundle directory.
pack <dir> Pack a directory into a .iflx, optionally signed.
test <dir> Run manifest.tests against an in-process sandbox. Exits non-zero on any failure.
keygen Generate an Ed25519 keypair and write <prefix>.public.iflk + <prefix>.private.iflk (private file is 0600).
sign <bundle> Sign a directory or unsigned .iflx.
verify <bundle> Inspect a .iflx — manifest, files, capabilities, signature. With --key, verify the embedded signature matches the expected public key fingerprint.
capabilities List every capability in the catalogue.

Common flags:

Flag Description
--json Machine-readable output (validate / test / verify)
--bail Stop on first test failure (ext test)
--out <file> Output path (pack / sign / keygen)
--key <file> Key file path (sign / verify)
--id <id> Override the manifest id during ext init
--name <name> Override the manifest name during ext init

The full design lives in Authoring Extensions. For the security model — capability grammar, sandbox limits, signing semantics — see the threat-model RFC.


layer — Layered Change Tracking

Publish content-addressed layers with provenance manifests over a local layer store (.ifc-lite/ of the cwd, override with --store <dir>), diff composed states, merge candidates into refs, and derive log/bake/revert/rebase from the same state-based op model.

ifc-lite layer create --base main --intent "Relocate fire doors"
ifc-lite layer publish delta.ifcx --base main --intent "Relocate fire doors" --check requirements.ids=report.json
ifc-lite layer diff main --against candidate-layer-id --json
ifc-lite layer merge candidate-layer-id --into main --preview
ifc-lite layer log main --json

Subcommands:

Subcommand Purpose
publish <delta.ifcx> Publish a delta as a content-addressed layer. --base <ref\|->, --intent "<text>", --scope <claim> (repeatable), --check <spec.ids>=<report.json> (repeatable), --principal <id>, --kind human\|agent\|hybrid, --strict-scope, --json
create Record a draft descriptor (.ifc-lite/draft.json). --base <ref>, --intent "<text>", --scope <claim>
status Show the draft and whether its base ref moved
diff <side> Diff composed states; a side is a ref, layer id, or .ifcx file. --against <side>, --components, --json
merge <layer-id> Merge a candidate into a ref (fast-forward or three-way plan). --into <ref>, --preview, --resolve ours\|theirs, --waive <spec> --reason "<text>", --approved-by <principal>, --allow-unrelated, --json
push <ref\|layer-id> Upload a ref's stack (or one layer) plus its check evidence to a layer registry. --registry <url>, --token <bearer>, --set-ref, --json
log <ref> Provenance log, newest first. --json
bake <ref> -o <out> Materialize a tombstone-free flat document
revert <layer-id> Publish an inverse layer and append it to a ref. --in <ref>, --resolve ours\|theirs, --json
rebase <layer-id> Re-plan a candidate onto a ref's current stack and publish the rebased layer. --onto <ref>, --json

All subcommands honour --store <dir> (default <cwd>/.ifc-lite). Exit codes: 0 clean, 2 conflicts, 3 policy failure, 4 scope violation (with --strict-scope), 5 unrelated merge base (override with --allow-unrelated), and 1 generic errors.


ref — Manage Named Refs

Manage named refs (branch-like pointers onto a layer stack) in the layer store.

ifc-lite ref list --json
ifc-lite ref create feature-x --from main
ifc-lite ref protect main --require-check requirements.ids --require-human-approval

Subcommands:

Subcommand Purpose
list List refs with layer counts and stack hashes. --json, --store <dir>
create <name> Create a ref, optionally copying another ref's layer stack (--from <ref>)
move <name> Point a ref at another ref's stack or at a comma-separated list of layer ids (--to <target>)
protect <name> Set merge policy on a ref. --require-check <spec> (repeatable), --require-human-approval

All subcommands honour --store <dir> (default <cwd>/.ifc-lite).


eval — Evaluate Expressions

Evaluate JavaScript expressions against the BIM SDK. The bim object provides the full @ifc-lite/sdk API.

# Count walls
ifc-lite eval model.ifc "bim.query().byType('IfcWall').count()"

# List storey names
ifc-lite eval model.ifc "bim.storeys().map(s => s.name)"

# Get properties of a specific entity
ifc-lite eval model.ifc "bim.properties({modelId:'default', expressId:42})"

# Evaluate an IfcCostItem; amounts are decimal strings
ifc-lite eval tests/models/cost/buildingsmart-cost-composition.ifc "bim.cost.evaluateItem({modelId:'default',expressId:42})" --json

# Complex query
ifc-lite eval model.ifc "bim.query().byType('IfcDoor').toArray().filter(d => d.name.includes('Fire'))"

Cost reads use the loaded IFC source snapshot; pending generic mutation overlays are not included until the source is replaced or reloaded.

Power Move for LLMs

The eval command is the most flexible tool. LLMs can write arbitrary SDK code and execute it without needing dedicated subcommands. The full API is discoverable via ifc-lite schema.


ask - Natural Language Queries

Answer common BIM questions in plain language. A recipe engine maps question patterns to SDK operations; no external AI service is involved.

ifc-lite ask model.ifc "how many walls?"
ifc-lite ask model.ifc "what is the window-wall ratio?" --json
ifc-lite ask model.ifc "list materials" --explain

Flags:

Flag Description
--json Machine-readable output
--explain Show which recipe matched and how the answer was computed

run — Execute Scripts

Run JavaScript files with the full bim SDK available.

ifc-lite run analysis.js model.ifc

Example script (analysis.js):

const walls = bim.query().byType('IfcWall').toArray();
console.log(`Found ${walls.length} walls`);

for (const wall of walls) {
  const props = bim.properties(wall.ref);
  const psetCommon = props.find(p => p.name === 'Pset_WallCommon');
  const isExternal = psetCommon?.properties.find(p => p.name === 'IsExternal');
  console.log(`  ${wall.name}: external=${isExternal?.value ?? 'unknown'}`);
}

const storeys = bim.storeys();
console.log(`\n${storeys.length} storeys:`);
for (const s of storeys) {
  const elements = bim.contains(s.ref);
  console.log(`  ${s.name}: ${elements.length} elements`);
}

schema — API Schema

Dump the complete SDK API schema as JSON. Useful for LLM tools to discover available methods.

ifc-lite schema              # Full schema with params and return types
ifc-lite schema --compact    # Minimal: names and descriptions only

The dump matches the bim object run/eval hand to scripts: a root bim namespace with its top-level methods (entity, properties, contains, on, …), plus the runtime SDK namespaces model, query, viewer, mutate, store, lens, create, files, schedule, clash, export — where query is the builder chain reached via bim.query() (.byType(...).where(...).toArray()), not a flat namespace — and their methods with parameter names, return types, and LLM semantic hints.


mcp - MCP Server

Start a Model Context Protocol server bound to one or more IFC files, so MCP-capable agents (Claude Code, Cursor, etc.) can query and edit the models through tools.

ifc-lite mcp model.ifc
ifc-lite mcp model.ifc --read-only
ifc-lite mcp arch.ifc struct.ifc
ifc-lite mcp model.ifc --transport http --port 8765 --token abc
ifc-lite mcp model.ifc --viewer          # also start the 3D viewer

HTTP transport starts with an empty session

In --transport http mode the positional files are not preloaded: every HTTP session gets its own empty model registry. Load a model into the session with the model_load tool (which needs mutate scope, so it is hidden under --read-only). The default stdio transport does preload the files you pass.

Flags:

Flag Description
--transport <t> stdio (default) or http
--port <N> HTTP port (default 8765)
--host <h> HTTP host (default 127.0.0.1; non-loopback requires --token or --insecure)
--token <bearer> HTTP bearer token for full scope
--insecure Allow non-loopback bind without a token (development only)
--read-only Hide mutation tools
--bsdd <url> Override the bSDD endpoint
--allow <path> Restrict file-system access (repeatable)
--viewer Auto-open the 3D viewer
--viewer-port <N> Preferred viewer port (0 = auto)
--open Auto-open the viewer and open the URL in the browser

simplify - Demesher

Selectively simplify element meshes and write a lighter IFC (geometry is re-authored as IfcTriangulatedFaceSet; IFC2X3 input is upconverted to IFC4):

ifc-lite simplify model.ifc --out light.ifc --level 3
ifc-lite simplify model.ifc --out light.ifc --level 5 --ids 12,44,107 --json
Option Description
--out <file> Output IFC path (required)
--level <1..5> Aggressiveness: 1-4 = cavity removal + decimation (target 50/25/10/3 % of triangles), 5 = bounding-box collapse (default 1)
--ids <a,b,...> Only simplify these express ids (default: all meshed elements)
--json Machine-readable report

What "lighter" means: the goal is TRIANGLE COUNT (viewer/render load), and on tessellation-heavy elements the reduction is large. It is not guaranteed per element: meshes below 32 triangles pass through levels 1-4 unchanged, and level 5 always emits a 12-triangle box, which can exceed a smaller input. File size usually drops on tessellation-heavy models (scans, CAD imports), but small parametric models can GROW in bytes, since verbose explicit tessellation replaces compact swept solids. Check the reported trianglesBefore/trianglesAfter and output size for your workload.

gym - Reset/Step/Reward Environment Loop

A prototype environment API for RL-style consumers: wrap a model, apply data-mutation ops over stdin, and get each step scored against the same schema/clash/ids checks that validate, clash, and ids run.

ifc-lite gym --model model.ifc --checks schema,clash
ifc-lite gym --model model.ifc --checks schema,clash,ids --ids rules.ids
ifc-lite gym --seed 42 --checks schema,clash   # generated episode (repo checkout only)

The protocol is newline-delimited JSON, one object per line in both directions: gym opens with a reset line (observation + baseline reward channels), the consumer sends step messages with ops (setProperty/setAttribute/deleteProperty, mirroring bim.mutate's method names), and gym replies with a reward line per step. reset reloads the pristine model, close exits. Malformed input yields a structured error line, never a crash. The same model plus the same op sequence produces byte-identical reward lines.

--seed <n> (plus optional --family, --corrupt/--no-corrupt/ --corrupt-rate) generates a deterministic world-gym episode in-process instead of loading a file; mid-session {"type":"reset","seed":8} swaps to a fresh generated episode. Episode generation dynamically imports tools/world-gym/ from a repo checkout; the published npm package prints a clear error for --seed while --model keeps working. See the @ifc-lite/cli README for the full protocol reference.


delivery — Repeatable Delivery Check

Run a saved, versioned model-delivery check: structural validation (the same rules validate runs), IDS validation (the same validator ids runs), and/or .rules.json information-validation rule sets (the same engine check runs) — any combination, against one or more models, in one invocation — with a consolidated JSON report and an optional standalone HTML report.

ifc-lite delivery recipe.json
ifc-lite delivery recipe.json --json
ifc-lite delivery recipe.json --json --html report.html
ifc-lite delivery recipe.json --out report.json --html report.html

The recipe is a small JSON file, versioned alongside the models it checks:

{
  "models": ["model.ifc"],
  "structural": true,
  "ids": ["door-rules.ids"],
  "rules": ["fire-rating.rules.json"]
}

models, ids and rules paths resolve relative to the recipe file's own directory, not the current working directory. A recipe must declare at least one applicable check ("structural": true and/or a non-empty "ids"/"rules" list) — a zero-check recipe is a fatal error rather than a silent "pass".

Every check reports one of three outcomes, never folded together:

  • pass — the check ran and found nothing to report.
  • fail — the check ran and found a violation (a structural error, a failed IDS specification, or a .rules.json rule).
  • error — the check could not be run: an unreadable/empty/corrupt model, an unreadable or unparsable IDS/rules file, an IDS document declaring zero specifications, a rule set declaring zero rules, or a rule the engine itself could not evaluate. An unevaluable check is never counted as a pass.

The overall verdict is pass only when every declared check on every declared model passed. An unreadable model, an empty IDS ruleset/rule set, or any failed check all produce a fail verdict — a delivery check can never report success on zero evidence.

Each recipe's rules file runs against ONE model at a time, mirroring the ids loop — a rule federated across every model in the recipe (targets, unique scope 'federation') is check's job, not delivery's.

The consolidated report records, per model, its declared path and a SHA-256 fingerprint of the bytes actually checked (or the load error, when unreadable); per check, the model, check type, source (the IDS/rules file), status, and the underlying validate/ids/check evidence (issues / specification / rule counts). Given the same recipe and the same bytes on disk, running delivery twice produces byte-identical --json output.

A committed worked example — a passing model, a structurally-broken model, an unreadable model, an IDS specification that fails, and a .rules.json rule that passes — lives at packages/cli/examples/delivery/.

Flags:

Flag Description
--json Print the consolidated report as JSON instead of a human-readable summary
--out <file> Write the JSON report to a file instead of stdout
--html <file> Additionally write a standalone HTML report to <file>

check — Rule-Set Validation

Run a .rules.json information-validation rule set — the SAME @ifc-lite/rules engine the viewer's Data Validation panel runs (#5138 PR 7b) — against one or more already-parsed models, in one invocation. No second evaluator: a rule set authored (or exported) in the viewer runs identically from the terminal.

ifc-lite check model.ifc --rules fire-rating.rules.json
ifc-lite check arch.ifc struct.ifc --rules fire-rating.rules.json --format json
ifc-lite check model.ifc --rules fire-rating.rules.json --fail-on warning

Every model is loaded and parsed before any rule runs; targets / modelTagIds in the rule set narrow which of the loaded models a given rule applies to, and unique/aggregate requirements with scope 'federation' (the default) see every model passed on the command line as one federation — the same semantics the viewer runs, unlike delivery's rules field, which checks one model at a time.

Exit codes, tri-state and never folded together:

Exit Meaning
0 Every rule passed (or was not applicable).
1 At least one rule failed at a severity --fail-on counts. Default --fail-on error: severity: "warning" rules never fail the run by themselves; pass --fail-on warning to make them count too.
2 A model could not be read/parsed, or the engine itself could not evaluate a rule (e.g. a ReDoS-rejected regex) — this always wins over 1, since an unevaluated rule is not pass/fail evidence.

Flags:

Flag Description
--rules <file> Path to the .rules.json rule set (required)
--format json\|table table (default) prints one line per rule: status, applicable/passed/failed counts, set-check failures; json prints the full ValidationReport verbatim
--fail-on error\|warning Which rule severities count toward exit code 1 (default error)
---

flow — Run a Node Graph Headlessly

Evaluate a *.flow.json node graph (see Flow graphs) against a model with the same @ifc-lite/flow-nodes library the viewer's editor uses. Viewer nodes (viewer.colorize, …) run as no-ops headlessly and pass their entities through, so a graph that highlights failures in the viewer runs unchanged in CI.

# Player-style inputs override node params; --out writes the model with the graph's mutations
ifc-lite flow run audit.flow.json model.ifc --input rating.value=REI90 --out audited.ifc --json

# The graph's inputs/outputs schema (what a Player form or a Hops-style caller needs)
ifc-lite flow describe audit.flow.json --json

# Document validation plus a per-node availability report for this host
ifc-lite flow validate audit.flow.json

Subcommands:

Subcommand Purpose
run <graph> <file.ifc> Evaluate the graph. --input <nodeId.param=value> (repeatable; JSON values parse, others are strings), --out <file> (IFC with the run's mutations applied), --tracking <file> / --no-tracking (element sets of tracked creation nodes; default <graph>.tracking.json beside the graph), --json
describe <graph> Print declared inputs (with param defaults) and outputs (with value kind and access). --json
run ... --checkpoint <file> A graph with AI nodes pauses for review: the run saves a review checkpoint to <file>, prints the proposal and its digest, and exits 3. A run that wrote to the model or opened a different model before pausing also needs --out; the resume must start from that file. --ai-max-requests / --ai-max-output-tokens set the run's AI root budget (default 12 requests, 24,000 output tokens)
review <checkpoint> --approve <digest> / --reject Approve exactly the proposal that was shown (the digest must match) or decline it. Without a flag it prints the checkpoint. --json
resume <graph> <file.ifc> --checkpoint <file> Claim an approved checkpoint once, check that the graph, --input values and model are the ones the pause recorded, restore every completed node without running it again (no AI request is sent for the reviewed proposal), and run what was paused. --out, --next-checkpoint <file> when the graph pauses again
validate <graph> Validate the document's wiring against the node registry (unknown types, missing ports, type-incompatible edges, unconnected required inputs, inputs/outputs markers that name nothing) and report each node as ok, noop, unavailable or unknown on this host. Exit 2 on any wiring problem or unrunnable node. --json

run exits 1 when any node failed; per-lane errors inside a lifted node do not fail the run (the lane yields null) and are listed under errors in the summary. Secrets are resolved from environment variables; a node that requires one the host lacks is reported as unavailable.

AI nodes (ai.classify, ai.summarize, ai.extract) run only when the environment names an OpenAI-compatible provider: IFC_LITE_AI_MODEL and IFC_LITE_AI_API_KEY (and optionally IFC_LITE_AI_BASE_URL, default OpenRouter). IFC_LITE_AI_STRUCTURED_OUTPUT=true enables JSON Schema requests for a compatible upstream model; false disables them. Only the exact https://api.openai.com/v1 endpoint defaults to enabled; other endpoints default to parser-only. These settings also apply to MCP. Without the required model and key the graph is refused before the model is opened. The key never appears in output or in a checkpoint.

ifc-lite flow run roles.flow.json model.ifc --checkpoint roles.checkpoint.json   # exit 3: paused for review
ifc-lite flow review roles.checkpoint.json                                       # print the proposal and its digest
ifc-lite flow review roles.checkpoint.json --approve <digest>
ifc-lite flow resume roles.flow.json model.ifc --checkpoint roles.checkpoint.json --out roles.ifc

A graph with a tracked creation node (model.addElement) re-run on its own output updates the elements it made — same GlobalIds, changed geometry — and removes the ones whose lanes vanished:

ifc-lite flow run columns.flow.json model.ifc --out v1.ifc
ifc-lite flow run columns.flow.json v1.ifc --input column.height=4 --out v2.ifc
ifc-lite diff v1.ifc v2.ifc --by-entity --json   # 0 added, 0 removed

Output Modes

Every command supports structured output:

Mode Flag Use Case
Table (default) Human-readable terminal output
JSON --json Machine-readable, pipe to jq
CSV --format csv Spreadsheet-compatible

Design principles:

  • stdout = data (JSON, CSV, tables)
  • stderr = status messages, progress
  • Exit 0 = success, Exit 1 = failure

Pipe Examples

# Count walls across multiple files
for f in *.ifc; do
  count=$(ifc-lite query "$f" --type IfcWall --count)
  echo "$f: $count walls"
done

# Extract all door names as plain text
ifc-lite query model.ifc --type IfcDoor --json | jq -r '.[].name'

# Export walls to CSV, filter with standard tools
ifc-lite export model.ifc --format csv --type IfcWall | grep "External"

# Chain: create an element, then inspect it
ifc-lite create wall --out /tmp/w.ifc --height 3 --thickness 0.2
ifc-lite info /tmp/w.ifc --json

# Merge and validate
ifc-lite merge arch.ifc struct.ifc --out fed.ifc && ifc-lite validate fed.ifc

# Convert and diff
ifc-lite convert model.ifc --schema IFC4 --out v4.ifc
ifc-lite diff model.ifc v4.ifc --json

# Look up bSDD data for wall types
ifc-lite bsdd psets IfcWall --json | jq '.["Pset_WallCommon"]'

Using with LLM Terminals

The CLI is designed to work seamlessly with AI coding assistants like Claude Code.

Discovery

An LLM can discover all capabilities by running:

ifc-lite --help          # Overview of all commands
ifc-lite schema          # Full API schema as JSON

Recommended CLAUDE.md Entry

Add this to your project's CLAUDE.md to help Claude Code use ifc-lite:

## IFC Analysis

Use `ifc-lite` CLI for BIM/IFC file operations:
- `ifc-lite info <file>` - model summary
- `ifc-lite stats <file>` - model KPIs and health check
- `ifc-lite query <file> --type <T> --json` - query entities
- `ifc-lite query <file> --type <T> --all --json` - full entity data
- `ifc-lite props <file> --id <N>` - single entity details
- `ifc-lite export <file> --format csv --type <T>` - export data
- `ifc-lite lod <file> --level 0|1 --out <file>` - generate LOD0/LOD1 artifacts
- `ifc-lite create <type> --out <file>` - create IFC elements (29 types)
- `ifc-lite mutate <file> --id <N> --set P=V --out <file>` - edit properties
- `ifc-lite merge <files...> --out <file>` - merge IFC files
- `ifc-lite convert <file> --schema <VER> --out <file>` - convert schema
- `ifc-lite diff <file1> <file2>` - compare IFC files
- `ifc-lite validate <file>` - structural validation
- `ifc-lite clash <file> --matrix --json` - clash detection
- `ifc-lite extract-entities <file> --product <GUID> --out <file>` - isolate entities into a small IFC
- `ifc-lite bsdd class <IfcType>` - bSDD class info
- `ifc-lite view <file> --port <N>` - launch 3D viewer with REST API
- `ifc-lite analyze <file> --viewer <port> --type <T>` - visual analysis overlay
- `ifc-lite eval <file> "<expr>"` - evaluate SDK expressions
- `ifc-lite ask <file> "<question>"` - natural language queries
- `ifc-lite schema` - discover all SDK methods
- `ifc-lite mcp <file>` - start an MCP server on the model

Always use `--json` for machine-readable output.
Run `ifc-lite schema` to see the full API before writing eval expressions.

Best Practices for LLM Usage

  1. Always use --json — structured output is easier to parse
  2. Use eval for complex queries — more flexible than building flags
  3. Run schema first — discover the API before writing code
  4. Pipe to jq — for filtering and transforming JSON output
  5. Use --count for quick checks — avoid loading full entity data when just counting
  6. Use --all with query — get complete entity data in one call
  7. Use create --from-json — for programmatic element creation from generated JSON

Command Reference

The semantic command reads JSON/SPARQL providers, generates shared profile artifacts, validates JSON or RDF, and operates an authenticated HTTPS relay. See Headless semantic records for flags, result formats, credential references, and reproduction examples.

Command Description
info Model summary (schema, entities, storeys)
query Query entities by type/properties/quantities
props All properties for a single entity
export Export data / geometry / energy model
schedule Tabular schedule of one class (csv/json/md/html)
diagnose-geometry CSG / opening diagnostics (failures, classification)
extract-entities Isolate entities into a small, viewable standalone IFC
anonymize Export selected objects + context as an anonymized IFC
ids Validate against IDS rules
bcf Work with BCF collaboration files
clash Detect geometric clashes between elements
create Create IFC elements (29 types)
eval Evaluate SDK expression
run Execute a script against model
schema Dump SDK API schema (for LLM tools)
merge Merge multiple IFC files
convert Convert between IFC schema versions
diff Compare two IFC files
rekey Carry a table keyed on old element keys across a revision
validate Structural validation checks
bsdd buildingSMART Data Dictionary lookup
stats Auto-calculated model KPIs and health check
mutate Modify properties/attributes and save
generate-spaces Derive IfcSpace from walls (slab/roof-aware height)
ask Natural language BIM queries
view Interactive 3D viewer in browser
analyze Query + visualize analysis results
lod Generate lightweight LOD artifacts
simplify Demesher: simplify meshes, write lighter IFC
mcp Start an MCP server bound to one or more IFC files
ext Manage IFClite extensions (Phase 0 — validate, init)
layer Layered change tracking over a local store (.ifc-lite/)
ref Manage named refs in the layer store
gym reset/step/reward environment loop (JSONL over stdin/stdout)
delivery Repeatable delivery check (structural + IDS + rule sets) from a saved recipe
check Run a .rules.json information-validation rule set (same engine as the viewer)
semantic Shared semantic profiles, SHACL and JSON/SPARQL providers
flow Evaluate a node graph headlessly; pause and resume reviewed AI proposals