"""Run in Blender, not system Python: blender --background --python blender/prepare_model.py Creates an editable .blend next to this script. Existing scene is replaced. Keeps every configuration variant; helper collections are hidden in viewport. """ from pathlib import Path import json import bpy ROOT=Path(__file__).resolve().parent.parent SOURCE=ROOT/'models'/'dum.glb' TARGET=Path(__file__).resolve().parent/'dum_locenice.blend' if not SOURCE.is_file(): raise FileNotFoundError(SOURCE) if TARGET.exists(): raise FileExistsError(f'Rename or remove {TARGET} before running again.') bpy.ops.wm.read_factory_settings(use_empty=True) bpy.ops.import_scene.gltf(filepath=str(SOURCE)) defaults=json.loads((ROOT/'data'/'catalog.json').read_text())['defaults'] scene=bpy.context.scene scene.unit_settings.system='METRIC' scene.unit_settings.scale_length=1.0 collections={} for name in ['01_Exterior','02_Interior_walls','03_Kitchen_and_furniture','04_Site','05_Cutaway_alternatives','06_Standards_alternatives']: col=bpy.data.collections.new(name);scene.collection.children.link(col);collections[name]=col for obj in list(scene.objects): if obj.type!='MESH':continue props=obj if props.get('stdHidden') or props.get('stdWhen') or props.get('stdBlind'): key='06_Standards_alternatives' elif props.get('interiorLayer') in ['cut','cutShell']: key='05_Cutaway_alternatives' elif props.get('interiorLayer')=='furniture': key='03_Kitchen_and_furniture' elif props.get('interiorLayer'): key='02_Interior_walls' elif props.get('siteLayer') or props.get('legacySite'): key='04_Site' else:key='01_Exterior' for c in list(obj.users_collection):c.objects.unlink(obj) collections[key].objects.link(obj) # Stable ID comes from the GLB extras. Do not delete it during future editing. obj['locenice_edit_note']='Keep loceniceId and configuration properties.' condition=props.get('stdWhen',{}) hidden=bool(props.get('stdHidden')) or bool(props.get('stdBlind') and not defaults['blinds']) if hasattr(condition,'items'): hidden=hidden or any(defaults.get(k)!=v for k,v in condition.items()) obj.hide_set(hidden) obj.hide_render=hidden collections['05_Cutaway_alternatives'].hide_viewport=True # Keep matching default variants visible; all alternatives remain in the file. note=bpy.data.texts.new('READ_ME_Locenice') note.write('''This is a visualisation mesh, not BIM. Units: metres. The source includes multiple, mutually exclusive variants in the same location. Use named collections to inspect them. Do not join all meshes or all materials. loceniceId, stdWhen, stdBlind, windowNode and interiorLayer must stay on objects. Detailed construction dimensions come from the project, not reference photos. After exporting all variants to GLB, run tools/prepare_export.py. Blender reorders glTF nodes, materials and textures. The included web manifest is indexed for the supplied GLB; the preparation tool rebinds stable IDs. A changed wall or cabinet also requires checking web collision volumes / camera presets. ''') scene['locenice_metadata_json']=json.dumps(json.loads((ROOT/'data'/'scene.json').read_text()),ensure_ascii=False) bpy.ops.wm.save_as_mainfile(filepath=str(TARGET)) print('Saved:',TARGET)