subagent transcript

session 17f09f89 · workflow wf_1ced79bb
delegated task
FreeCAD is ported to WebAssembly (JSPI + native-EH, static CPython 3.14, no dlopen, no subprocess, single-thread, Qt 6.11). 17 workbenches already wor
▸ Magik
FreeCAD is ported to WebAssembly (JSPI + native-EH, static CPython 3.14, no dlopen, no subprocess, single-thread, Qt 6.11). 17 workbenches already work incl. Part/PartDesign/Sketcher/Measure/Draft/Assembly/TechDraw/Mesh + numpy + PySide6 + pivy._coin. Source tree: /home/magik6k/lcad-wasm/freecad-port/FreeCAD (branch wasm-parity-push). Build tree (already configured, DO NOT build — read only): /opt/toolchains/src/freecad-gui-build. The module-enablement pipeline is documented in the memory files at /home/magik6k/.claude/projects/-home-magik6k-lcad-wasm/memory/ (read freecad-wasm-parity-roadmap.md and freecad-wasm-viewport.md FIRST — they describe the src/Main/CMakeLists.txt inittab generator, FREECAD_WASM_EXTRA_PY_MODULES / FREECAD_WASM_EXTRA_LINK_LIBS, SHARED->STATIC conversion, data-packaging with file_packager, and known gotchas). Constraints that shape feasibility: NO subprocess/fork/exec, NO dlopen (all C-ext modules static via inittab), NO threads (QThreadPool/QtConcurrent absent), NO listen sockets, wasm32 4GB ceiling. C++ deps must be new-EH (-fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0). Pure-Python addons are fine; native ones need static linking. Your job: READ ONLY. Do not edit source, do not run cmake/ninja/emcc. Produce an actionable plan a build engineer can execute. Deep-dive BIM/Arch for wasm. Known: an eager addonmanager import in nativeifc/ifc_openshell.py breaks import; ifcopenshell (native) is unported; lark/packaging need packaging. Determine the MINIMUM to get BIM/Arch to at least LOAD (workbench + non-IFC Arch tools like Wall/Structure/Window working) even if IFC read/write is stubbed. Which Python imports fail at InitGui time and how to guard them under a wasm/no-ifcopenshell path? Which pure-Python deps (lark, packaging, ...) must be added to the /python data package? Give exact files + guard edits + the list of Python packages to bundle. Read src/Mod/BIM (and Arch) InitGui.py + the offending import chains.
Fable
thinking
I'm going to start by reading through the memory files and relevant source code, parallelizing the reads to speed things up.
Fable
I'll start by reading the relevant memory files and the BIM/Arch source.
Fable
⚙ Bash {"command": "cat /home/magik6k/.claude/projects/-home-magik6k-lcad-wasm/memory/freecad-wasm-parity-roadmap.md; echo \"=====VIEWPORT=====\"; cat /home/magik6k/.claude/projects/-home-magik6k-lcad-wasm/memory/freecad-wasm-viewport.md", "descri…
tool result
---
name: freecad-wasm-parity-roadmap
description: "Evidence-based roadmap to full FreeCAD desktop parity in wasm — current ~35%, ~80% ceiling, pivy._coin is the linchpin, phase order, hard blockers"
metadata:
  node_type: memory
  type: project
  originSessionId: 17f09f89-ce7c-4f49-aa6a-cf0521026805
---

Roadmap researched 2026-07-05 (7-agent workflow over the actual FreeCAD source + wasm build + deploy). Rendered as an artifact for the user. Current desktop parity ≈ **35%** (usage-weighted; th  …[truncated]
Fable
⚙ Bash {"command": "ls /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/ 2>/dev/null; echo \"=====ARCH?=====\"; ls /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/Arch/ 2>/dev/null", "description": "List BIM and Arch module directorie…
tool result
Exit code 2
ArchAxis.py
ArchAxisSystem.py
ArchBuildingPart.py
ArchBuilding.py
ArchCommands.py
ArchComponent.py
ArchCoveringGui.py
ArchCovering.py
ArchCurtainWall.py
ArchCutPlane.py
arch.dox
ArchEquipment.py
ArchFence.py
ArchFloor.py
ArchFrame.py
ArchGrid.py
ArchIFC.py
ArchIFCSchema.py
ArchIFCView.py
ArchMaterial.py
ArchNesting.py
ArchPanel.py
ArchPipe.py
ArchPrecast.py
ArchProfile.py
ArchProject.py
Arch.py
ArchRebar.py
ArchReference.py
ArchReport.py
ArchRoof.py
ArchSchedule.py
ArchSectionPlane.p  …[truncated]
Fable
thinking
I need to look at the BIM module structure in FreeCAD, specifically checking the InitGui.py and Init.py files along with the import chains that are causing the issue.
Fable
BIM absorbed Arch. Let me read the key init files and the offending import chains.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/InitGui.py"}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	# ***************************************************************************
4	# *                                                                         *
5	# *   Copyright (c) 2017 Yorik van Havre <yorik@uncreated.net>              *
6	# *                                                                         *
7	# *   This file is part of FreeCAD.                                         *
8	# *                                               …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/Init.py"}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	# ***************************************************************************
4	# *                                                                         *
5	# *   Copyright (c) 2022 Yorik van Havre <yorik@uncreated.net>              *
6	# *                                                                         *
7	# *   This file is part of FreeCAD.                                         *
8	# *                                               …[truncated]
Fable
⚙ Bash {"command": "ls /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/nativeifc/ /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/bimcommands/ /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/importers/ /home/magik6k/lcad…
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/bimcommands/:
BimArchUtils.py
BimAxis.py
BimBackground.py
BimBeam.py
BimBox.py
BimBuilder.py
BimBuildingPart.py
BimClassification.py
BimClone.py
BimColumn.py
BimCommon.py
BimCompound.py
BimConvert.py
BimCopy.py
BimCovering.py
BimCurtainwall.py
BimCutPlane.py
BimCut.py
BimDiff.py
BimDimensions.py
BimDoor.py
BimDrawingView.py
BimEmptyTrash.py
BimEquipment.py
BimExamples.py
BimExtrude.py
BimFence.py
BimFrame.py
BimFuse.py
BimGlue.py
BimHelp.p  …[truncated]
Fable
thinking
I'm tracing through the import chains that get triggered during `createTools()` to identify the problematic dependencies. The main entry points are `DraftTools`, `bimcommands`, `ifc_commands` from nativeifc, and `init_tools` from draftutils, plus `Arch_rc` at the BIM InitGui level. I need to dig into `nativeifc/ifc_commands.py` to see what's pulling in these heavy dependencies.
Fable
Now let me trace the offending import chains: nativeifc, bimcommands, and the eager addonmanager import.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/nativeifc/ifc_openshell.py"}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	# ***************************************************************************
4	# *                                                                         *
5	# *   Copyright (c) 2024 Yorik van Havre <yorik@uncreated.net>              *
6	# *                                                                         *
7	# *   This file is part of FreeCAD.                                         *
8	# *                                               …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/nativeifc/ifc_commands.py"}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	# ***************************************************************************
4	# *                                                                         *
5	# *   Copyright (c) 2023 Yorik van Havre <yorik@uncreated.net>              *
6	# *                                                                         *
7	# *   This file is part of FreeCAD.                                         *
8	# *                                               …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/bimcommands/__init__.py"}
tool result
1	# SPDX-License-Identifier: LGPL-2.1-or-later
2	
3	import glob
4	import os
5	
6	import Arch_rc
7	import PartGui
8	
9	# populate the list of submodules
10	modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py"))
11	__all__ = [
12	    os.path.basename(f)[:-3] for f in modules if os.path.isfile(f) and not f.endswith("__init__.py")
13	]
14	
15	from . import *
16	
Fable
thinking
I'm tracing through the import chain in InitGui.py's `createTools()` method. When it imports DraftTools, that's already ported and fine. But importing bimcommands triggers a cascade—its `__init__.py` loads Arch_rc and PartGui, then uses a glob import to pull in all the Bim*.py command modules, and any of those that eagerly import ifcopenshell or nativeifc will break. Then there's the direct import from nativeifc itself.

Looking at the actual failure points: ifc_commands.py imports ifc_openshell at the module level, which immediately tries to import packaging.version and addonmanager_utilities—that's the eager AddonManager dependency that's causing the crash. I need to check what nativeifc/_  …
Fable
Let me read nativeifc/__init__.py and grep for the problematic top-level imports across BIM.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/nativeifc/__init__.py"}
tool result
1	"""Shared NativeIFC availability helpers."""
2	
3	import importlib.util
4	
5	import FreeCAD
6	
7	translate = FreeCAD.Qt.translate
8	
9	_ifcopenshell_state = {"available": None, "reported_missing": False}
10	
11	
12	def invalidate_ifcopenshell_cache():
13	    """Clears the cached ifcopenshell availability state."""
14	
15	    _ifcopenshell_state["available"] = None
16	    _ifcopenshell_state["reported_missing"] = False
17	
18	
19	def has_ifcopenshell(report=False):
20	    """Returns True when i  …[truncated]
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== grep ifcopenshell imports (module-level, whole tree) =====\"\ngrep -rn \"^import ifcopenshell\\|^import ifcopenshell\\.\\|^from ifcopenshell\\|^ impor…
tool result
===== grep ifcopenshell imports (module-level, whole tree) =====
(eval):3: no matches found: --include=*.py

===== grep lark =====
(eval):6: no matches found: --include=*.py

===== grep packaging =====
(eval):9: no matches found: --include=*.py

===== grep addonmanager =====
(eval):12: no matches found: --include=*.py
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
I'm reconsidering the approach here—instead of relying on zsh globbing, I'll use find combined with grep to search through files more reliably.
Fable
The `nativeifc/__init__.py` already has a lazy `has_ifcopenshell` guard. Good. Let me fix the grep (zsh glob issue).
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== module-top-level ifcopenshell imports (col 1 or shallow indent) =====\"\ngrep -rn \"import ifcopenshell\" . --include=\"*.py\" 2>/dev/null | grep -vE \"^…
tool result
===== module-top-level ifcopenshell imports (col 1 or shallow indent) =====
ArchReference.py:440:            import ifcopenshell
bimcommands/BimClassification.py:485:        import ifcopenshell
bimcommands/BimSetup.py:111:            import ifcopenshell
bimcommands/BimSetup.py:609:                import ifcopenshell
bimtests/TestArchBuildingPart.py:28:import ifcopenshell
nativeifc/ifc_geometry.py:27:import ifcopenshell
nativeifc/ifc_geometry.py:28:import ifcopenshell.util.unit
importers/exportIF  …[truncated]
Fable
thinking
I can see the pattern now—most of the `ifcopenshell` imports are tucked inside functions, which is fine since they won't load at initialization time. But there are a few module-level imports that are causing the problem: they're scattered across `ifc_geometry.py`, `ifc_diff.py`, `ifc_layers.py`, and `ifc_generator.py`.

The real issue surfaces when I trace the InitGui import chain. When `bimcommands` gets imported, it pulls in all the Bim*.py files, but those seem to keep their `ifcopenshell` imports safely inside functions. However, the critical path is through `ifc_commands.py`, which imports `ifc_openshell.py` at the top level—and that's where `packaging.version` and `addonmanager_utiliti  …
Fable
Now let me check ifc_observer (loaded in Activated), top-level nativeifc/addonmanager imports in bimcommands, and where addonmanager_utilities lives.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== ifc_observer.py top imports =====\"\nsed -n '24,60p' nativeifc/ifc_observer.py\necho\necho \"===== bimcommands/*.py TOP-LEVEL (unindented) imports of nat…
tool result
===== ifc_observer.py top imports =====

"""Document observer to act on documents containing NativeIFC objects"""

import FreeCAD
from . import has_ifcopenshell

params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC")


def add_observer():
    """Adds this observer to the running FreeCAD instance"""

    if not has_ifcopenshell(report=True):
        return

    FreeCAD.BIMobserver = ifc_observer()
    FreeCAD.addDocumentObserver(FreeCAD.BIMobserver)


def remove_observer():  …[truncated]
Fable
thinking
The imports in ifc_observer.py are properly guarded behind a has_ifcopenshell check, and all the bimcommands nativeifc imports are nested inside functions rather than at the top level, which keeps things wasm-compatible. The real issue is isolated to ifc_openshell.py where there's a top-level import of Version from packaging that needs to be addressed.

Now I need to figure out whether packaging is actually bundled in the wasm Python distribution and whether addonmanager_utilities is even available in the wasm environment — AddonManager might not be loaded at all. The safest approach would be to guard that import in ifc_openshell.py. I should also verify that the dependency chain from InitGu  …
Fable
ifc_observer is already guarded. The only hard InitGui breakage is ifc_openshell.py top-level. Let me verify the deploy bundle (is `packaging` present?), Arch_rc, and scan bimcommands top-level imports for any other surprises.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== ALL top-level imports across bimcommands/*.py (unique) =====\"\ngrep -hn \"^import \\|^from \" bimcommands/*.py | sed 's/[0-9]*://' | sort -u\necho\necho…
tool result
===== ALL top-level imports across bimcommands/*.py (unique) =====
from ArchStructure import StructureMode
from bimcommands import BimWindow
from draftguitools import gui_dimensions
from draftguitools import gui_lines  # Line tool from Draft
from draftguitools import gui_shape2dview
from enum import Enum
from __future__ import print_function
from . import *
import Arch
import Arch_rc
import ArchStructure
import ast
import csv
import DraftTools
import FreeCAD
import FreeCADGui
import glob
import   …[truncated]
Fable
thinking
I need to investigate the filesystem layout in the data packages to see where Python's standard library and site-packages are located, so I'm checking the .data.js manifests for path prefixes. I also realized that Arch_rc.py doesn't exist in the build tree because it's generated from Arch.qrc through the BIM CMakeLists, and since BIM hasn't been built yet, this is blocking the `import Arch_rc` statement.

Now I'm wondering how other workbenches handle their _rc modules—Draft has Draft_rc, Part has Part_rc, and these are all generated by CMake's resource compiler into the build directory and then packaged. I should check the freecad.data.js manifest to see how existing _rc.py files are being   …
Fable
Let me inspect the packaged FS layout and how other `_rc` modules are handled.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy\necho \"===== path prefixes in freecad.data.js =====\"\ngrep -oE '\"filename\":\"[^\"]*\"' freecad.data.js 2>/dev/null | sed 's/\"filename\"://' | sed 's#\\(/[^/]*/[^/]*/[^/]*\\).*…
tool result
===== path prefixes in freecad.data.js =====

===== _rc.py modules present anywhere in data.js manifests =====
freecad-mods-t2.data.js: "/freecad/Mod/Draft/Draft_rc.py"
freecad-mods-t3.data.js: "/freecad/Mod/Assembly/Assembly_rc.py"

===== packaging / lark / ifcopenshell in any manifest =====
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
So the resource compiler does generate `_rc.py` files that get packaged—`Draft_rc.py` is there, which means `Arch_rc.py` should be generated the same way, but since BIM isn't built yet, it doesn't exist. I also notice `packaging` isn't in any of the data packages, so that'll need to be bundled separately. And as expected, ifcopenshell isn't available since it hasn't been ported.

Now I need to understand the manifest format better and figure out where the Python standard library lives in the site-packages path, then examine how freecad.data.js is structured.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy\necho \"===== head of freecad.data.js (structure) =====\"\nhead -c 1200 freecad.data.js; echo\necho; echo \"===== python path prefixes across manifests =====\"\nfor f in *.data.js;…
tool result
===== head of freecad.data.js (structure) =====

  var Module = typeof Module != 'undefined' ? Module : {};

  Module['expectedDataFileDownloads'] ??= 0;
  Module['expectedDataFileDownloads']++;
  (() => {
    // Do not attempt to redownload the virtual filesystem data when in a pthread or a Wasm Worker context.
    var isPthread = typeof ENVIRONMENT_IS_PTHREAD != 'undefined' && ENVIRONMENT_IS_PTHREAD;
    var isWasmWorker = typeof ENVIRONMENT_IS_WASM_WORKER != 'undefined' && ENVIRONMENT_IS_WASM  …[truncated]
Fable
thinking
I'm seeing how the directory structure works now—Python packages live in `/python/`, so `packaging` would go into `/python/packaging`, while FreeCAD modules use `/freecad/Mod/<M>/`. The `_rc.py` files are generated per-module and bundled with each module, and since `/python` is already on sys.path, I can drop the pure-Python `packaging` package directly there.

Now I'm thinking through whether `packaging` is even necessary if I guard the imports. The top-level import in ifc_openshell.py pulls in `packaging.version`, but if I wrap that in a try-except or make it lazy, BIM could load without it. Since `packaging` is lightweight pure-Python anyway, it's worth bundling. The real problem is `addo  …
Fable
Now I understand the full layout. Let me confirm addonmanager_utilities' top-level imports (to judge portability) and check ifc_import.py / exportIFC guard status since they're registered importers.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod\necho \"===== addonmanager_utilities top-level imports =====\"\ngrep -nE \"^import |^from \" AddonManager/addonmanager_utilities.py | head -40\necho\necho \"===== create_p…
tool result
===== addonmanager_utilities top-level imports =====
27:from datetime import datetime
28:from typing import Optional, Any, List
29:import os
30:import platform
31:import shutil
32:import stat
33:import subprocess
34:import sys
35:import time
36:import re
37:import ctypes
39:from urllib.parse import urlparse
41:from PySideWrapper import QtCore, QtGui, QtWidgets, QtNetwork
43:import addonmanager_freecad_interface as fci

===== create_pip_call definition (does it need subprocess/network?) =====
582  …[truncated]
Fable
thinking
So the real solution is to add a guard around the import in ifc_openshell.py rather than trying to bundle addonmanager_utilities, since that module depends on PySideWrapper and other AddonManager components that aren't enabled. Let me verify if there are other imports happening at InitGui time that might have similar issues.

Looking at the full import chain: DraftTools loads fine, bimcommands uses `from . import *` which executes all the Bim*.py modules, and they all import standard FreeCAD modules and Arch-related packages that should be available. The real blocker is when nativeifc tries to import ifc_openshell, which immediately fails because packaging isn't bundled and addonmanager_util  …
Fable
Let me confirm BIM is pure-Python (no native lib) and how Arch_rc.py is generated.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== any App/ or Gui/ C++ subdir? =====\"\nls -d App Gui 2>/dev/null || echo \"NONE — pure Python module\"\necho\necho \"===== CMakeLists.txt (how Arch_rc + i…
tool result
===== any App/ or Gui/ C++ subdir? =====
NONE — pure Python module

===== CMakeLists.txt (how Arch_rc + install work) =====
5:# Define the source files and resources of the BIM workbench
8:# If we are building the GUI mode, we need to process the Qt resource file
9:# Note that if any of the files listed in the .qrc file changes, the
10:# resource file will NOT be regenerated automatically. To work around this,
11:# use `touch` on the .qrc file to force regeneration.
12:IF (BUILD_GUI)
13:    PYSI  …[truncated]
Fable
thinking
BIM is a pure-Python workbench that only needs the `Arch_rc.py` resource file generated from the QRC file. The key blocker is guarding the top-level imports in `ifc_openshell.py` to prevent packaging and addon manager issues during initialization, then I need to run the cmake build step to generate that resource file.

I'm weighing whether to bundle the `packaging` module or guard its import too — since it's pure Python and lightweight, bundling it alongside guarding both imports in `ifc_openshell.py` gives us redundant safety. The trickier part is handling the `IFC_UpdateIOS` command registration: if the addon manager import fails, I need to prevent the command from being registered entirel  …
Fable
Confirmed: BIM is pure-Python; the only build artifact is `Arch_rc.py` from `PYSIDE_WRAP_RC`. Let me check the module dependency gating and the BUILD flag name.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD\necho \"===== BIM/Arch dependency gating =====\"\ngrep -niE \"bim|arch\" cMake/FreeCAD_Helpers/CheckInterModuleDependencies.cmake 2>/dev/null | head\necho\necho \"===== BUILD_BIM …
tool result
===== BIM/Arch dependency gating =====
21:    REQUIRES_MODS(BUILD_BIM                BUILD_PART BUILD_MESH BUILD_MESH_PART BUILD_DRAFT)

===== BUILD_BIM option definition =====
cMake/FreeCAD_Helpers/CheckInterModuleDependencies.cmake:21:    REQUIRES_MODS(BUILD_BIM                BUILD_PART BUILD_MESH BUILD_MESH_PART BUILD_DRAFT)
cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake:150:    option(BUILD_BIM "Build the FreeCAD BIM module" ON)

===== is BUILD_BIM currently ON in build tree cach  …[truncated]
Fable
thinking
Checking the BIM module dependencies — PART, MESH, MESH_PART, and DRAFT are all already built in, so enabling BIM with the cmake flag won't hit any missing dependencies. Now I'm looking at the Arch.py imports, which includes a self-import of Arch and wildcard imports from ArchCommands, though the thinking cuts off mid-line.

Looking at the ArchSql module, I need to verify whether it has a runtime dependency on lark. A grep search across BIM only found lark references in ArchSqlParserGenerator.py, which is a build-time script that generates ArchSql.py. This means ArchSql.py itself is a standalone generated parser with no lark runtime dependency — lark is purely a development tool, not somethi  …
Fable
`lark` is only used by a build-time generator script (`ArchSqlParserGenerator.py`), not at runtime — `ArchSql.py` is the pre-generated standalone parser. Let me confirm.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM\necho \"===== ArchSql.py: any lark import? =====\"\ngrep -nE \"import lark|from lark\" ArchSql.py || echo \"NO lark import — self-contained generated parser\"\necho \"…
tool result
===== ArchSql.py: any lark import? =====
NO lark import — self-contained generated parser
===== head of ArchSql.py =====
1:# SPDX-License-Identifier: MIT
2:#
3:# Copyright (c) 2019 Daniel Furtlehner (furti)
4:# Copyright (c) 2025 The FreeCAD Project
5:#
6:# This file is a derivative work of the sql_parser.py file from the
7:# FreeCAD-Reporting workbench (https://github.com/furti/FreeCAD-Reporting).
8:# As per the terms of the original MIT license, this derivative work is also
9:# licensed under   …[truncated]
Fable
thinking
So `packaging` is pure Python with no compiled extensions, and `lark` is only needed at build time, while `ArchSql.py` stands alone. I've got what I need for the plan now. The `packaging` module is required by ifc_openshell for version handling, though technically only `version.py` and `_structures.py` are essential—but including the whole thing is negligible at 1.4M.

Actually, if I guard the ifc_openshell import lazily, `packaging` becomes optional since the only other runtime use is in BimPreflight.py inside a function that only runs during IFC preflight checks, which is stubbed anyway. I'll recommend bundling it anyway to avoid potential crashes, but it's not strictly required for loadin  …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD\necho \"===== Draft_rc.py: source or build-generated? =====\"\nfind . -name \"Draft_rc.py\" 2>/dev/null; find /opt/toolchains/src/freecad-gui-build -name \"Draft_rc.py\" 2>/dev/nu…
tool result
===== Draft_rc.py: source or build-generated? =====
./src/Mod/Draft/Draft_rc.py
/opt/toolchains/src/freecad-gui-build/Mod/Draft/Draft_rc.py
/opt/toolchains/src/freecad-gui-build/src/Mod/Draft/Draft_rc.py

===== pack-mods.sh (how it collects files, does it grab _rc.py?) =====
#!/usr/bin/env bash
# Build a supplementary emscripten preload package containing the Python trees
# of FreeCAD modules that were enabled AFTER the base freecad.data was built.
# Icons/.ui are compiled into the *Gui.a via qr  …[truncated]
Fable
thinking
So `Draft_rc.py` is present in both the source directory and the build tree—the resource compiler outputs it directly into the source, which is why pack-mods.sh picked it up from there. But `Arch_rc.py` doesn't exist in source yet, so after the BIM build runs, it should only appear in the build directory.

The issue is that pack-mods.sh only syncs from the source tree, so the generated `Arch_rc.py` won't get included unless we either copy it into `src/Mod/BIM/` beforehand or modify the script to also pull from the build output. That's the concrete gotcha to watch for.

I have what I need now—this is definitely tractable. It's a pure Python module with one guard edit, resource generation, and  …
Fable
I have the complete picture. Let me compile the findings. ## Summary of investigation **BIM absorbed Arch** — it's a single pure-Python workbench at `src/Mod/BIM/` (Arch\*.py + bimcommands/ + nativeifc/ + importers/). No `App/` or `Gui/` C++ — **no inittab/native linking needed**. The only build artifact is `Arch_rc.py` (Qt resource, embeds icons/ui/translations) from `PYSIDE_WRAP_RC(Resources/Arch.qrc)`. **The single hard InitGui load blocker:** `createTools()` runs `from nativeifc import ifc_commands` → `from . import ifc_openshell` → and `ifc_openshell.py` has two **eager module-top imports** that throw with no ifcopenshell/AddonManager: - line 27 `from packaging.version import Version` (packaging not bundled) - line 31 `from addonmanager_utilities import create_pip_call` (AddonManager not on sys.path; it itself pulls subprocess/ctypes/PySideWrapper — not portable, must NOT be bundled) **Everything else is already lazy or already guarded:** - `nativeifc/__init__.py` has a clean `has_ifcopenshell()` (importlib.util.find_spec) guard — already wasm-friendly plumbing. - `nativeifc/ifc_observer.py` (loaded in `Activated()`) early-returns via `has_ifcopenshell(report=True)`. - `nativeifc/ifc_tools.py` wraps ifcopenshell in try/except → `report_missing_ifcopenshell(); raise`. - All `import ifcopenshell` in bimcommands/*.py and importers/*.py are function-local (lazy). No bimcommands module imports nativeifc/packaging/ifcopenshell at top level (verified). - `Init.py` registers importers/exporters as lazy string module-paths. **`lark` is a red herring** — only `Resources/ArchSqlParserGenerator.py` (a dev-time generator) imports it; `ArchSql.py` is the pre-generated self-contained parser with no runtime lark dep. Do NOT bundle lark. **`packaging`** is pure-Python (no C-ext), only otherwise used lazily in `BimPreflight.py:310`. Not strictly required to LOAD once ifc_openshell is guarded, but cheap insurance. BIM's cmake deps (`REQUIRES_MODS BUILD_PART BUILD_MESH BUILD_MESH_PART BUILD_DRAFT`) are all already ON, so `-DBUILD_BIM=ON` won't fail gating. Data layout: Python pkgs → `/python/`, modules → `/freecad/Mod/<M>/`. `Draft_rc.py` precedent confirms `_rc.py` ships as packaged `.py`.
Fable
⚙ StructuredOutput {"area": "BIM/Arch workbench load-enablement for FreeCAD-wasm (non-IFC tools; IFC stubbed)", "verdict": "tractable-with-work", "effortHours": "3-6", "valueToUsers": "high", "rootBlockers": ["ifc_openshell.py eager top-level imports: `from p…
tool result
Structured output provided successfully