subagent transcript
session 17f09f89 · workflow wf_1bf8c0e4
delegated task
Context: FreeCAD is ported to WebAssembly (emscripten, CPython 3.14, PySide6/shiboken, JSPI). Source tree at /home/magik6k/lcad-wasm/freecad-port/Free
▸ Magik
Context: FreeCAD is ported to WebAssembly (emscripten, CPython 3.14, PySide6/shiboken, JSPI). Source tree at /home/magik6k/lcad-wasm/freecad-port/FreeCAD.
The CAM/BIM GUI workbench-activation CRASH is already fixed (a modal QMessageBox during async activation tore the app down; now logged instead). CAM & BIM now activate (done=OK). These tasks are the REMAINING NON-CRASH functionality gaps.
The deployed Python modules live in file_packager .data packages (freecad-mods*.data). The wasm FS is emscripten MEMFS/IDBFS (IndexedDB-backed) mounted around /tmp/.local, /tmp/.config etc. Note: BIM's Arch_rc (a ~45MB Qt resource containing icons AND the :/ui/*.ui preference-page resources) was DROPPED from packaging to save size — this is why ':/ui/...' resources are missing.
Investigate READ-ONLY. Return a concrete, minimal, wasm-appropriate fix plan (exact file paths + exact code).
TASK: Design the fix for BIM's remaining functionality gap.
Symptom (at BIM workbench Initialize): 'ArchIFCSchema.py:40 IfcVersion = ifcVersions[params.get_param_arch("IfcVersion")]' raises "list indices must be integers or slices, not NoneType" because get_param_arch("IfcVersion") returns None (boot warning: "draftutils.params.get_param: Unable to find 'IfcVersion' in 'Mod/Arch'"). Also boot: "During initialization the error 'UI file does not exist' occurred in /freecad/Mod/BIM/InitGui.py" (from FreeCADGui.addPreferencePage(":/ui/preferences-arch.ui", ...) et al, InitGui.py:601-848).
Investigate:
1. Exactly HOW draftutils/params.py builds PARAM_DICT (read _get_param_dictionary at src/Mod/Draft/draftutils/params.py:455+) and where "Mod/Arch" params (incl. IfcVersion default) come from — a bundled .ui/QRC resource? a hardcoded dict? Determine why "Mod/Arch" ends up without IfcVersion.
2. The set of Arch/BIM params that would ALSO return None (broad breakage) vs just IfcVersion.
3. Best fix: is it (a) a small robust guard (e.g. default IfcVersion to index 0/"IFC4" when None), (b) loading the Arch param defaults from a bundled source that IS packaged, or (c) re-packaging just the small .ui files (not the 45MB icons)? Weigh comprehensiveness vs simplicity. Give the EXACT code change for the recommended fix (and note if addPreferencePage failures are harmless vs need guarding).
Verify BimBeam.py:29 'import ArchStructure' -> ArchStructure.py:84 'Presets=ArchProfile.readPresets()' path and the ArchIFCSchema import chain from bimcommands.
Fable
thinking
I'm starting to investigate the BIM/Arch parameters issue in FreeCAD-wasm by examining the key files involved—the Draft utilities params module, the Arch IFC schema, and the BIM initialization code where the problem likely originates.
Fable
I'll investigate the BIM params issue read-only. Let me start by examining the key files in parallel.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 400, "limit": 200}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Arch/ArchIFCSchema.py", "offset": 1, "limit": 80}
Fable
thinking
I need to continue examining params.py and locate the ArchIFCSchema.py file.
Fable
Let me continue reading params.py and locate ArchIFCSchema.py.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 599, "limit": 160}
Fable
⚙ Bash {"command": "find /home/magik6k/lcad-wasm/freecad-port/FreeCAD -name ArchIFCSchema.py 2>/dev/null; echo \"---BimBeam---\"; find /home/magik6k/lcad-wasm/freecad-port/FreeCAD -name BimBeam.py 2>/dev/null; echo \"---ArchStructure---\"; find /h…
Fable
thinking
I need to dig into how the dictionary gets built and cached in params.py, particularly looking at the get_param function and understanding the PARAM_DICT global variable and get_param_arch now that Arch has been folded into the BIM module.
Fable
Now let me read the rest of params.py (how the dict is finalized/cached) and the get_param functions at the top.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 758, "limit": 120}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 1, "limit": 120}
Fable
thinking
So the `PARAM_DICT` gets populated once when the module loads, pulling from `_get_param_dictionary()`. The hardcoded "Mod/Arch" dictionary doesn't include "IfcVersion", which means it has to be coming from parsing the `.ui` resource file instead.
The critical issue is that `import Arch_rc` is wrapped in a try/except that silently swallows `ModuleNotFoundError`. If that 45MB resource module isn't packaged, the import passes silently but the Qt resource system never registers those `:/ui/...` paths. When the code tries to open those resource files at line 706, it fails and skips them entirely. This means any parameter that exists only in the `.ui` files (like "IfcVersion" from preferences-arc …
Fable
Now let me read ArchIFCSchema.py (the crash site), and check the .ui files and Arch_rc packaging status.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/ArchIFCSchema.py", "offset": 1, "limit": 80}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD; echo \"=== grep IfcVersion in .ui files ===\"; grep -rl \"IfcVersion\" src/Mod/BIM/Resources/ui/ 2>/dev/null; echo \"=== grep IfcVersion prefEntry context ===\"; grep -rn \"IfcVe…
Fable
thinking
I'm tracing the root cause: when `get_param_arch("IfcVersion")` returns None, the code tries to use it as a list index on `ifcVersions = ["IFC4", "IFC2X3"]`, which throws a TypeError. The IfcVersion parameter is only defined in the preferences UI file, so if it's missing or not properly initialized, this indexing operation fails.
Fable
Let me examine the preferences-arch.ui IfcVersion widget and the PrefComboBox parser to know the exact default.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/Resources/ui/preferences-arch.ui", "offset": 205, "limit": 70}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD; echo \"=== _param_from_PrefComboBox ===\"; grep -n \"_param_from_PrefComboBox\\|def _param_from_Pref\" src/Mod/Draft/draftutils/params.py | head; echo \"=== show the PrefComboBox…
Fable
thinking
The awk command didn't capture the body content, so I need to look directly at the _param_from_PrefComboBox function to see what's happening in that section.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 265, "limit": 40}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD; echo \"=== Does Arch_rc get imported anywhere else / how BIM registers resources ===\"; grep -rn \"Arch_rc\\|import.*_rc\\b\" src/Mod/BIM/*.py src/Mod/BIM/InitGui.py 2>/dev/null …
Fable
thinking
So InitGui.py already has the try/except protection around Arch_rc thanks to the wasm port changes, and params.py has it too. The IfcVersion combobox in the .ui file doesn't specify a currentIndex property, which means the default value of 0 gets applied—pointing to IFC4 as the initial selection.
The real issue is broader than just IfcVersion. All the Arch and IFC parameters that are defined only in the .ui files (preferences-arch.ui, preferences-archdefaults.ui, preferences-dae.ui, preferences-ifc.ui, preferences-ifc-export.ui, and preferences-sh3d-import) will return None when Arch_rc is missing, since they're not in the hardcoded param_dict. I need to identify which of these params are a …
Fable
Now let me enumerate the full breakage set: all params sourced only from the Arch/IFC .ui files (gated behind the missing Arch_rc) vs. the hardcoded fallback dict. Let me also confirm the ArchStructure/ArchProfile import chain.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/Resources/ui\necho \"=== All (prefPath::prefEntry) pairs across the Arch/IFC preference .ui files ===\"\nfor f in preferences-arch.ui preferences-archdefaults.ui prefe…
Fable
thinking
When Arch_rc is missing, all the parameters sourced from the .ui files with Mod/Arch or Mod/BIM prefixes return None from the get_param functions, but the hardcoded param_dict only has a different subset—mostly the ones not in preferences like DoorHeight and WallHeight—so it's missing all the .ui-sourced ones, which is causing this widespread breakage.
The impact is broad: roughly 100 Arch/BIM parameters return None, and while most callers handle None gracefully (treating it as falsy), some use it as an index or in arithmetic operations and crash. IfcVersion is the critical one since it's used as a list index at import time. I'm weighing three fix options: adding a guard to default IfcVersi …
Fable
The breakage is broad. Now let me verify the import chains the task asked about (ArchStructure/ArchProfile/ArchIFCSchema) and check how InitGui handles the addPreferencePage failures, plus find other IfcVersion consumers and index-style get_param_arch uses.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== ArchStructure.py imports & line 84 ===\"; sed -n '1,90p' ArchStructure.py | grep -n \"import\\|ArchIFC\\|readPresets\\|Presets\" | head -40\necho; echo \"=…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== other IfcVersion consumers ===\"; grep -rn \"IfcVersion\\|get_param_arch(\\\"Ifc\\|getStandardType\" *.py | grep -i ifcversion | head\necho; echo \"=== get…
Fable
thinking
I'm tracing through the import dependencies starting from BimBeam, which imports ArchStructure, which in turn imports ArchComponent and loads presets from ArchProfile—following the chain to understand what gets pulled in when BimBeam initializes.
The key issue is that ArchComponent imports ArchIFC, which imports ArchIFCSchema, and that's where the crash happens at module load time because IfcVersion is the only parameter sourced from the UI that's used at import-time and isn't protected by the hardcoded fallback dictionary. The readPresets path is safe since it just reads CSV files from disk with proper existence checks, so the real culprit is the ArchIFCSchema import chain triggering befor …
Fable
Let me confirm the exact import chain into ArchIFCSchema and check InitGui's addPreferencePage handling and the Arch_rc guard.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== ArchComponent imports of ArchIFC/ArchIFCSchema (top-level?) ===\"; grep -n \"import ArchIFC\" ArchComponent.py\necho; echo \"=== ArchIFC.py imports ArchIFC…
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py", "offset": 590, "limit": 50}
Fable
thinking
I'm tracing through the initialization chain to understand where the "UI file does not exist" error gets caught. The task points to InitGui.py lines 601-848 for the addPreferencePage function, so I need to check if loadPreferences is wrapped in error handling and how FreeCAD's Initialize() method catches exceptions during workbench startup.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== all addPreferencePage in InitGui.py ===\"; grep -n \"addPreferencePage\" InitGui.py\necho; echo \"=== where loadPreferences called + Initialize + try/excep…
Fable
thinking
I'm tracing through the Initialize method to see if the loadPreferences call at line 60 is wrapped in error handling, and whether an exception from addPreferencePage would prevent the rest of the initialization from running.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py", "offset": 50, "limit": 75}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py", "offset": 835, "limit": 20}
Fable
thinking
So the issue is in the Initialize() method where createTools() is called first, then loadPreferences() tries to load a UI file that doesn't exist—specifically ":/ui/preferences-arch.ui"—which triggers the exception that gets caught by the error handler in the boot log.
But wait, I'm realizing the actual crash happens earlier: when createTools() imports bimcommands, which cascades through ArchStructure, ArchComponent, ArchIFC, and finally ArchIFCSchema where the TypeError occurs at line 40 due to IfcVersion being None. This means the ArchIFCSchema crash happens before loadPreferences is even reached, so how are both errors appearing in the boot log?
The answer is that the "UI file does not …
Fable
Both errors trace cleanly, but I need to verify one critical thing: whether `addPreferencePage` raises a hard Python exception (which would abort the rest of `Initialize` once the ArchIFCSchema crash is fixed) or just logs. Let me check the C++ side.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src\necho \"=== addPreferencePage C++ impl ===\"; grep -rn \"UI file does not exist\\|addPreferencePage\" Gui/Application.cpp Gui/ApplicationPy.cpp 2>/dev/null | head\necho; echo …
Fable
thinking
I need to look at the C++ code around line 1287 to understand exactly when the exception gets raised.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/ApplicationPy.cpp", "offset": 1268, "limit": 40}
Fable
thinking
So the issue is that `addPreferencePage()` fails because the resource file containing the UI doesn't exist yet — Arch_rc hasn't been loaded. Once I fix ArchIFCSchema:40, the initialization should get past `createTools()` and reach `loadPreferences()`.
But here's the critical part: when `addPreferencePage()` raises that RuntimeError, it doesn't just skip that one call — it aborts the entire `loadPreferences()` function, which then aborts the rest of `Initialize()`. That means `_param_observer_start()`, the draft preference pages, and `updateLocale()` all get skipped.
So I need a two-part fix: first, resolve the ArchIFCSchema:40 crash, and second, guard the `addPreferencePage()` calls so a m …
Fable
`addPreferencePage` raises `RuntimeError` when the resource is absent. This is decisive: once the ArchIFCSchema crash is fixed, `createTools()` will succeed and `Initialize()` will proceed to `loadPreferences()` (line 60), where line 601 will raise and abort the rest of `Initialize`. So both must be fixed. Let me verify the remaining pieces: the Presets JSON exist, no other import-time param-index crashes, and the ArchProfile file path.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== ifc_*_IFC4.json presets present? ===\"; ls -la Presets/ifc_contexts_IFC4.json Presets/ifc_products_IFC4.json Presets/ifc_types_IFC4.json 2>&1\necho; echo \…
Fable
thinking
Looking at the ArchVRM module, the DEBUG variable at line 52 retrieves a parameter that doesn't exist in the hardcoded dictionary, so it returns None—which works fine as a falsy boolean flag and won't cause a crash. The only actual import-time crash appears to be in ArchIFCSchema at line 40.
For the other module-level parameter calls in ArchComponent and ArchStructure, they're actually inside methods that execute lazily when objects are created, not during workbench initialization. Even when they do run, they assign None to object attributes, which degrades gracefully rather than crashing. The broader issue is that roughly 100 UI-only parameters return None throughout the codebase, but at i …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"=== total size of the 7 arch/ifc preference .ui files (option c cost) ===\"\ndu -ch Resources/ui/preferences-arch.ui Resources/ui/preferences-archdefaults.ui R…
Fable
thinking
I've confirmed that all the UI files are stored in Arch.qrc, which compiles to a 45MB resource file with icons, while the seven architecture and IFC preference UI files only add 140KB—so creating a slim resource with just those UI prefixes would be efficient. Now I'm checking how readPresets resolves the profilefiles path, whether it looks in getResourceDir/Mod/BIM/Presets/profiles.csv or in the local Presets directory relative to the module file.
The readPresets call is actually guarded by os.path.exists, so it's safe either way and not the source of the crash. The real issue is that ArchIFCSchema crashes at line 40 during the import chain: importing ArchStructure triggers importing ArchCo …
Fable
I now have a complete, evidence-backed picture. Let me compile the findings into the structured fix plan.
Key evidence gathered:
- `params.py:773` builds `PARAM_DICT` once at import; `param_dict["Mod/Arch"]` (lines 555-635) is the hardcoded fallback and does NOT contain `IfcVersion`.
- `IfcVersion` only exists in `preferences-arch.ui:220` (a `Gui::PrefComboBox`, no `currentIndex` → default index 0 = "IFC4"), parsed via the `:/ui/...` loop (params.py:682-770), which needs `Arch_rc` — imported under `try/except ModuleNotFoundError` (params.py:33-36) and dropped on wasm.
- `addPreferencePage` raises `RuntimeError: "UI file does not exist"` (ApplicationPy.cpp:1287) when the resource is absent — not a soft log.
- Crash chain confirmed: `bimcommands`→`BimBeam.py:29 import ArchStructure`→`ArchStructure.py:42 import ArchComponent`→`ArchComponent.py:48 import ArchIFC`→`ArchIFC.py:34 import ArchIFCSchema`→`ArchIFCSchema.py:40` crash (top-level). `readPresets()` at ArchStructure.py:84 is never reached and is independently safe (os.path.exists-guarded).
Fable
⚙ StructuredOutput {"root_cause": "TWO symptoms, ONE root cause: the Arch_rc Qt resource (compiled from src/Mod/BIM/Resources/Arch.qrc; ~45MB icons + the :/ui/*.ui preference pages) was dropped from wasm packaging.\n\n(1) The fatal crash. draftutils/params.py…