Files
FreeCADWasmDev/tools/prepare_bim_probe_resources.py
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

77 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Stage official source/runtime files for the headless BIM probe."""
from __future__ import annotations
import argparse
import hashlib
import shutil
import subprocess
import sys
from pathlib import Path
def copy_tree(source: Path, target: Path) -> None:
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(source, target, dirs_exist_ok=True, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
def copy_file(source: Path, target: Path) -> None:
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
source = root / "freecad-source"
python_root = source / "build-web" / "python-install" / "lib" / "python3.12"
output = args.output.resolve()
if output.exists():
shutil.rmtree(output)
copy_tree(source / "src" / "Mod" / "BIM", output / "share" / "Mod" / "BIM")
parser_source = source / "src" / "Mod" / "BIM" / "Resources" / "ArchSqlParserGenerator.py"
grammar = source / "src" / "Mod" / "BIM" / "Resources" / "ArchSql.lark"
generated_parser = output / "share" / "Mod" / "BIM" / "generated_sql_parser.py"
subprocess.run([sys.executable, str(parser_source), str(grammar), str(generated_parser)], check=True)
copy_tree(source / "src" / "Mod" / "Draft", output / "freecad-mod" / "Draft")
copy_tree(source / "src" / "3rdParty" / "lazy_loader", output / "freecad-mod" / "lazy_loader")
schema_candidates = (
root.parent / "FreeCADBaseLib" / "sources" / "src" / "Qt-5.15.13" / "qt3d" / "src" / "3rdparty" / "assimp" / "src" / "scripts" / "StepImporter" / "schema_ifc2x3.exp",
root / "FreeCADBaseLib" / "sources" / "src" / "Qt-5.15.13" / "qt3d" / "src" / "3rdparty" / "assimp" / "src" / "scripts" / "StepImporter" / "schema_ifc2x3.exp",
)
schema = next((path for path in schema_candidates if path.exists()), None)
if schema is None:
raise FileNotFoundError("official IFC EXPRESS schema resource is unavailable")
copy_file(schema, output / "share" / "Mod" / "BIM" / "schema_ifc2x3.exp")
(output / "share" / "Mod" / "BIM" / "legacy-fixture.ifc").write_text(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(('FreeCAD Web legacy probe'),'2;1');\nENDSEC;\n"
"DATA;\n#1=IFCPERSON($,$,$,$,$,$,$,$);\nENDSEC;\nEND-ISO-10303-21;\n",
encoding="ascii",
)
# DAE imports xml.sax.saxutils -> urllib.request, which needs both email
# and http.client even when the document is read from a local file.
for name in (
"encodings", "json", "xml", "re", "collections", "logging", "importlib", "zipfile", "urllib",
"email", "http",
):
copy_tree(python_root / name, output / "freecad-python" / name)
# Stage every top-level pure-Python standard-library module produced by the
# same Emscripten Python build. Import chains such as Draft -> uuid ->
# random -> pickle are implementation details of that runtime and a manual
# allow-list repeatedly omitted valid dependencies. Native extension
# modules remain excluded because this only selects ``*.py`` files.
for module in sorted(python_root.glob("*.py")):
copy_file(module, output / "freecad-python" / module.name)
manifest = []
for resource in sorted(path for path in output.rglob("*") if path.is_file()):
digest = hashlib.sha256(resource.read_bytes()).hexdigest()
manifest.append(f"{digest} {resource.relative_to(output).as_posix()}")
(output / ".resource-manifest.sha256").write_text("\n".join(manifest) + "\n", encoding="ascii")
print(output)
if __name__ == "__main__":
main()