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}
tool result
400	
401	def _param_from_PrefFontBox(widget):
402	    if App.GuiUp:
403	        from PySide import QtGui
404	
405	        font = QtGui.QFont()
406	        font.setStyleHint(QtGui.QFont.StyleHint.SansSerif)
407	        value = font.defaultFamily()
408	    else:
409	        value = ""
410	    for elem in list(widget):
411	        if "name" in elem.keys():
412	            att_name = elem.attrib["name"]
413	            if att_name == "prefEntry":
414	                entry = elem.find("cstring").text  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Arch/ArchIFCSchema.py", "offset": 1, "limit": 80}
tool result
File does not exist. Note: your current working directory is /home/magik6k/lcad-wasm/freecad-port/FreeCAD.
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}
tool result
599	        "PrecastRiser":                ("float",     0.0),
600	        "PrecastTread":                ("float",     0.0),
601	        "ProfilePreset":               ("string",    ""),
602	        "ScheduleColumnWidth0":        ("int",       100),
603	        "ScheduleColumnWidth1":        ("int",       100),
604	        "ScheduleColumnWidth2":        ("int",       50),
605	        "ScheduleColumnWidth3":        ("int",       100),
606	        "ScheduleDialogHeight":        ("int",       200)  …[truncated]
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…
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/ArchIFCSchema.py
---BimBeam---
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/bimcommands/BimBeam.py
---ArchStructure---
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/ArchStructure.py
---ArchProfile---
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/ArchProfile.py
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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}
tool result
758	                elif att_class == "Gui::PrefCheckableGroupBox":
759	                    # It's a boolean preference, so we can reuse the parsing logic
760	                    # from _param_from_PrefCheckBox, which looks for <property name="checked">.
761	                    path, entry, value = _param_from_PrefCheckBox(widget)
762	                    typ = "bool"
763	
764	                if path is not None:
765	                    if path in param_dict:
766	                        param_dic  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Draft/draftutils/params.py", "offset": 1, "limit": 120}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	# ***************************************************************************
3	# *                                                                         *
4	# *   Copyright (c) 2023 FreeCAD Project Association                        *
5	# *                                                                         *
6	# *   This file is part of FreeCAD.                                         *
7	# *                                                  …[truncated]
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}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	# ***************************************************************************
4	# *                                                                         *
5	# *   Copyright (c) 2019 Dion Moult <dion@thinkmoult.com>                   *
6	# *   Copyright (c) 2019 Yorik van Havre <yorik@uncreated.net>              *
7	# *   Copyright (c) 2019 FreeCAD Developers                                 *
8	# *                                               …[truncated]
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…
tool result
=== grep IfcVersion in .ui files ===
src/Mod/BIM/Resources/ui/preferences-arch.ui
=== grep IfcVersion prefEntry context ===
src/Mod/BIM/Resources/ui/preferences-arch.ui:213:       <widget class="QLabel" name="label_IfcVersion">
src/Mod/BIM/Resources/ui/preferences-arch.ui:220:       <widget class="Gui::PrefComboBox" name="comboBox_IfcVersion">
src/Mod/BIM/Resources/ui/preferences-arch.ui:225:         <cstring>IfcVersion</cstring>
=== Arch_rc.py location(s) ===
./src/Mod/BIM/Resources/Arch.qrc
zs  …[truncated]
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}
tool result
205	         <cstring>ReferenceCheckInterval</cstring>
206	        </property>
207	        <property name="prefPath" stdset="0">
208	         <cstring>Mod/Arch</cstring>
209	        </property>
210	       </widget>
211	      </item>
212	      <item row="9" column="0">
213	       <widget class="QLabel" name="label_IfcVersion">
214	        <property name="text">
215	         <string>IFC version</string>
216	        </property>
217	       </widget>
218	      </item>
219	      <item row="9" column="  …[truncated]
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…
tool result
=== _param_from_PrefComboBox ===
265:def _param_from_PrefCheckBox(widget):
279:def _param_from_PrefRadioButton(widget):
283:def _param_from_PrefComboBox(widget):
297:def _param_from_PrefSpinBox(widget):
311:def _param_from_PrefDoubleSpinBox(widget):
325:def _param_from_PrefUnitSpinBox(widget):
339:def _param_from_PrefQuantitySpinBox(widget):
356:def _param_from_PrefColorButton(widget):
373:def _param_from_PrefLineEdit(widget):
389:def _param_from_PrefFileChooser(widget):
=== show the PrefComboBo  …[truncated]
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}
tool result
265	def _param_from_PrefCheckBox(widget):
266	    value = False
267	    for elem in list(widget):
268	        if "name" in elem.keys():
269	            att_name = elem.attrib["name"]
270	            if att_name == "checked":  # Can be missing.
271	                value = elem.find("bool").text == "true"
272	            elif att_name == "prefEntry":
273	                entry = elem.find("cstring").text
274	            elif att_name == "prefPath":
275	                path = elem.find("cstring").te  …[truncated]
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 …
tool result
=== Does Arch_rc get imported anywhere else / how BIM registers resources ===
src/Mod/BIM/ArchBuilding.py:333:        import Arch_rc
src/Mod/BIM/ArchBuilding.py:339:        import Arch_rc
src/Mod/BIM/ArchAxis.py:519:        import Arch_rc
src/Mod/BIM/ArchFloor.py:381:        import Arch_rc
src/Mod/BIM/ArchFloor.py:443:        import Arch_rc
src/Mod/BIM/ArchProject.py:130:        import Arch_rc
src/Mod/BIM/ArchFence.py:312:        import Arch_rc
src/Mod/BIM/ArchCurtainWall.py:762:        import A  …[truncated]
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…
tool result
=== All (prefPath::prefEntry) pairs across the Arch/IFC preference .ui files ===
preferences-arch.ui	Mod/Arch	autoJoinWalls	CheckBox
preferences-arch.ui	Mod/Arch	joinWallSketches	CheckBox
preferences-arch.ui	Mod/Arch	archRemoveExternal	CheckBox
preferences-arch.ui	Mod/Arch	UseMaterialColor	CheckBox
preferences-arch.ui	Mod/Arch	applyconstructionStyle	CheckBox
preferences-arch.ui	Mod/Arch	MoveWithHost	CheckBox
preferences-arch.ui	Mod/Arch	MoveBase	CheckBox
preferences-arch.ui	Mod/Arch	MaxComputeAr  …[truncated]
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 \"=…
tool result
=== ArchStructure.py imports & line 84 ===
40:import enum
41:import FreeCAD
42:import ArchComponent
43:import ArchCommands
44:import ArchProfile
45:import Draft
46:import DraftVecUtils
48:from FreeCAD import Vector
49:from draftutils import params
50:from draftutils import gui_utils
65:    from PySide import QtCore, QtGui
66:    from PySide.QtCore import QT_TRANSLATE_NOOP
67:    import FreeCADGui
68:    import ArchPrecast
69:    import draftguitools.gui_trackers as DraftTrackers
70:    from draf  …[truncated]
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…
tool result
=== other IfcVersion consumers ===
ArchIFCSchema.py:40:IfcVersion = ifcVersions[params.get_param_arch("IfcVersion")]
ArchIFCSchema.py:44:        FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "ifc_contexts_" + IfcVersion + ".json"
ArchIFCSchema.py:51:        FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "ifc_products_" + IfcVersion + ".json"
ArchIFCSchema.py:58:        FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "ifc_types_" + IfcVersion + ".json"

=== get_param_arch used as inde  …[truncated]
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…
tool result
=== ArchComponent imports of ArchIFC/ArchIFCSchema (top-level?) ===
48:import ArchIFC
2500:        import ArchIFCSchema

=== ArchIFC.py imports ArchIFCSchema (top-level?) ===
34:import ArchIFCSchema
48:IfcTypes = [uncamel(t) for t in ArchIFCSchema.IfcProducts.keys()]
326:            propertyType = "App::" + ArchIFCSchema.IfcTypes[attribute["type"]]["property"]
513:        return ArchIFCSchema.IfcProducts
533:        return ArchIFCSchema.IfcContexts

=== confirm ArchIFC import is module top-level  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py", "offset": 590, "limit": 50}
tool result
590	                lst = lst[:idx] + cmds + lst[idx + 1 :]
591	            setattr(self, attr, lst)
592	
593	    def loadPreferences(self):
594	        """Set up preferences pages"""
595	
596	        def QT_TRANSLATE_NOOP(context, text):
597	            return text
598	
599	        t1 = QT_TRANSLATE_NOOP("QObject", "BIM")
600	        t2 = QT_TRANSLATE_NOOP("QObject", "Draft")
601	        FreeCADGui.addPreferencePage(":/ui/preferences-arch.ui", t1)
602	        FreeCADGui.addPreferencePage(":/ui/  …[truncated]
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…
tool result
=== all addPreferencePage in InitGui.py ===
601:        FreeCADGui.addPreferencePage(":/ui/preferences-arch.ui", t1)
602:        FreeCADGui.addPreferencePage(":/ui/preferences-archdefaults.ui", t1)
603:        FreeCADGui.addPreferencePage(":/ui/preferencesNativeIFC.ui", t1)
610:        FreeCADGui.addPreferencePage(":/ui/preferences-draft.ui", t2)
611:        FreeCADGui.addPreferencePage(":/ui/preferences-draftinterface.ui", t2)
612:        FreeCADGui.addPreferencePage(":/ui/preferences-draftsnap  …[truncated]
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}
tool result
50	    def Initialize(self):
51	
52	        # add translations and icon paths
53	        FreeCADGui.addIconPath(":/icons")
54	        FreeCADGui.addLanguagePath(":/translations")
55	
56	        # Create menus and toolbars
57	        self.createTools()
58	
59	        # Load Arch & Draft preference pages
60	        self.loadPreferences()
61	
62	        Log("Loading BIM module… done\n")
63	        FreeCADGui.updateLocale()
64	
65	    def createTools(self):
66	        "Create tolbars and menus"
67	
  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py", "offset": 835, "limit": 20}
tool result
835	
836	FreeCADGui.addWorkbench(BIMWorkbench)
837	
838	# Preference pages for importing and exporting various file formats
839	# are independent of the loading of the workbench and can be loaded at startup
840	
841	
842	def QT_TRANSLATE_NOOP(context, text):
843	    return text
844	
845	
846	t = QT_TRANSLATE_NOOP("QObject", "Import-Export")
847	FreeCADGui.addPreferencePage(":/ui/preferences-ifc.ui", t)
848	FreeCADGui.addPreferencePage(":/ui/preferences-ifc-export.ui", t)
849	FreeCADGui.addPrefer  …[truncated]
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 …
tool result
=== addPreferencePage C++ impl ===
Gui/ApplicationPy.cpp:393:    {"addPreferencePage",
Gui/ApplicationPy.cpp:396:     "addPreferencePage(path, group) -> None\n"
Gui/ApplicationPy.cpp:397:     "addPreferencePage(dialog, group) -> None\n"
Gui/ApplicationPy.cpp:1287:            PyErr_SetString(PyExc_RuntimeError, "UI file does not exist");

=== find the string ===
Gui/ApplicationPy.cpp:1287:            PyErr_SetString(PyExc_RuntimeError, "UI file does not exist");
zshexit:1: parameter HISTDB_F  …[truncated]
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}
tool result
1268	        pPyResource = new PyResource();
1269	        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
1270	        static_cast<PyResource*>(pPyResource)->load(fn);
1271	    }
1272	    catch (const Base::Exception& e) {
1273	        PyErr_SetString(PyExc_AssertionError, e.what());
1274	        return nullptr;
1275	    }
1276	
1277	    return pPyResource;
1278	}
1279	
1280	PyObject* ApplicationPy::sAddPreferencePage(PyObject* /*self*/, PyObject* args)
1281	{
1282	    char* f  …[truncated]
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 \…
tool result
=== ifc_*_IFC4.json presets present? ===
-rw-r--r-- 1 magik6k magik6k   3984 Jul  3 17:58 Presets/ifc_contexts_IFC4.json
-rw-r--r-- 1 magik6k magik6k 380309 Jul  3 17:58 Presets/ifc_products_IFC4.json
-rw-r--r-- 1 magik6k magik6k  14650 Jul  3 17:58 Presets/ifc_types_IFC4.json

=== module-level (col-0 or class-body) get_param_arch across BIM (import-time risk) ===
ArchIFCSchema.py:40:IfcVersion = ifcVersions[params.get_param_arch("IfcVersion")]
ArchVRM.py:52:DEBUG = params.get_param_arch("ShowVR  …[truncated]
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…
tool result
=== total size of the 7 arch/ifc preference .ui files (option c cost) ===
140K	total

=== profilefiles (readPresets source) lines 64-74 ===
profilefiles = [
    os.path.join(FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "profiles.csv"),
    os.path.join(os.path.dirname(__file__), "Presets", "profiles.csv"),
    os.path.join(FreeCAD.getUserAppDataDir(), "BIM", "profiles.csv"),
]


def readPresets():

    Presets = []
    bid = 1  # Unique index
    for profilefile in profilefiles:
        if  …[truncated]
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…
tool result
Structured output provided successfully