Python scripts for IFC post-processing: layer assignment, cleanup, format conversion, and self-learning LLM classification.
# Windows (PowerShell / cmd)
.\.venv\Scripts\python.exe pyifclayer_llm.py # Full run: layers + LLM learning + writes _clean.ifc
.\.venv\Scripts\python.exe pyifclayer_llm.py --dry-run # Test run: LLM classifies but writes nothing
# Or from venv
.venv\Scripts\python.exe pyifclayer_llm.pyRequirements: Python 3.10+, ifcopenshell, requests, python-dotenv (install via pip install -r requirements.txt or pip install ifcopenshell requests python-dotenv)
Environment: Create .env with LLM_API_KEY=sk-or-... (OpenRouter key for Nemotron 3 Nano)
| Script | Purpose | Usage |
|---|---|---|
pyifclayer_llm.py |
Main pipeline: loads layers_rule.csv + layers_rule_learned.csv, assigns layers via rules, sends unassigned elements to LLM (Nemotron 3 Nano via OpenRouter), writes learned patterns to layers_rule_learned.csv, merges learned into base on real run. |
python pyifclayer_llm.py [--dry-run] |
pyifclayer.py |
Core layer assignment only (no LLM). Reads layers_rule.csv, assigns IfcPresentationLayerAssignment by element type/name patterns. Writes <file>_clean.ifc. |
python pyifclayer.py (processes all .ifc in cwd) |
llm_classify_prototype.py |
DRY-RUN prototype: loads rules, finds unassigned elements, calls LLM, prints results. Writes nothing. Good for testing prompt quality. | python llm_classify_prototype.py |
pyArchIFC2NavisIFC.py |
ArchiCAD → Navisworks IFC: strips storeys/layers/materials, groups elements into IfcElementAssembly by "Naviswork - уровни" pset, applies colors/coords from coords.txt/system.txt. |
python pyArchIFC2NavisIFC.py |
geonics.py |
NanoCAD GeoniCS IFC → target: converts IfcSpatialZone/IfcValve/IfcPipeSegment to proxies, creates building/storey, migrates to IFC2X3. |
python geonics.py |
ifc_delim.py |
Splits IFC by IfcPresentationLayerAssignment into separate files (one per layer). Draft (test.ifc). |
python ifc_delim.py |
stories.py |
Collapses all IfcBuildingStorey into single base storey (level 0). |
python stories.py |
allplan_ifc.py |
Text-based IFC fix for Allplan: IFCQUANTITY* → IFCPROPERTYSINGLEVALUE, IFCELEMENTQUANTITY → IFCPROPERTYSET. Works on raw strings. |
python allplan_ifc.py |
renga_ifc_compat.py |
Converts IfcSpace zones from Tessellation to SweptSolid (slab extrusion) for Renga compatibility. |
python renga_ifc_compat.py input.ifc output.ifc |
batchifctonwd |
Batch IFC→NWD via Navisworks FileToolsTaskRunner.exe. Paths hardcoded to C:\IFCTEMP. |
python batchifctonwd |
| File | Description |
|---|---|
layers_rule.csv |
Base layer rules (UTF-8/CP1251, ; delimiter). Columns: Layer Name;IfcType;Name;.... IfcType=All → fixed list; empty → all IfcProduct by name; specific type → exact match. |
layers_rule_learned.csv |
Learned patterns from LLM (appended to base = lower priority). Auto-managed. |
.env |
LLM_API_KEY=sk-or-... (OpenRouter). Not committed. |
Layer Name;IfcType;Name;Category;Workset;FamilyName
2_0_Колонны.КЖ;IfcColumn;;;;
2_0_Стены наружные.КЖ;IfcWall;;;;
5_1_Оборудование.ТХ;;.*приборы.*,.*рудование.*;;;
я82_Скрытый.КЖ;;.*скрытый.*;;;- Col 1: Layer name (must match IFC layer name)
- Col 2 (
IfcType):All= fixed list (IfcColumn,IfcSlab,IfcDiscreteAccessory,IfcBeam,IfcMember,IfcGrid); empty = search by name among ALLIfcProduct; specific type (e.g.IfcWall,ifcstair) = exact type match - Col 3 (
Name): Case-insensitive regex patterns forelement.Name - Col 4 (
Category): Case-insensitive regex patterns forCategoryPset property - Col 5 (
Workset): Case-insensitive regex patterns forWorksetPset property - Col 6 (
FamilyName): Case-insensitive regex patterns forFamilyNamePset property - Matching logic: A rule matches if ANY of its non-empty pattern sets matches (Name OR Category OR Workset OR FamilyName)
- Priority = row order (first match wins). Learned rules appended at end = lowest priority
Note: Rows with empty
IfcTypeAND all pattern columns empty are stored as "no-rule layers" for LLM validation only. They do NOT act as catch-all rules that would consume all subsequent rows.
layers_rule.csv + layers_rule_learned.csv
│
▼
set_layer() ── typed rules ──▶ assigned
│
▼
set_layer() ── allprod rules ──▶ assigned
│
▼
Unassigned elements ──▶ LLM resolver (Nemotron 3 Nano)
│
├─▶ LLM picks layer FROM allowed list (validated client-side)
└─▶ Client builds SAFE regex from element names (LCP + re.escape)
│
▼
layers_rule_learned.csv (append only, no duplicates)
│
▼ (on REAL run only, not --dry-run)
merge_learned_into_base() → append to layers_rule.csv, clear learned
Safety: LLM only chooses layer name. Regex pattern built by client (prevents ReDoS/injection). Layer name validated against CSV list.
Rate limits: Free OpenRouter = 1000 req/day. CHUNK=10, sleep(6) between batches (~10 rpm). 429 → exponential backoff ×5.
- Prepare: Place
.ifcin folder, ensurelayers_rule.csvexists,.envhasLLM_API_KEY - Test:
python pyifclayer_llm.py --dry-run— see LLM classification distribution, no files written - Run:
python pyifclayer_llm.py— writes<file>_clean.ifc, updateslayers_rule_learned.csv, merges learned into base - Iterate: Next run uses merged rules → fewer LLM calls
- All scripts work in-place on
*.ifcin current directory, output<name>_clean.ifc pyifclayer_llm.pyprocesses first.ifcfound (not all)- Encoding auto-detected: UTF-8 → CP1251 → CP1252 → Latin1
- Python: use
python(3.11) for most, butpy -3.10withPYTHONPATH=for ifcopenshell/numpy compatibility - LLM:free OpenRouter model:
nvidia/nemotron-3-nano-30b-a3b:free(1000 req/day)