voxcity.importer.loader ======================= .. py:module:: voxcity.importer.loader .. autoapi-nested-parse:: Load OBJ geometry groups and route them to import roles (e.g. building vs. window/non-building) before voxelization. An OBJ file may bundle multiple named objects/groups in a single export (e.g. a Rhino layer-per-group export). ``load_obj_groups`` recovers those as separate named meshes; ``classify_roles``/``select_building_groups`` let a caller mark some groups as non-building (windows, context, site furniture, ...) so only the actual building geometry gets voxelized. Attributes ---------- .. autoapisummary:: voxcity.importer.loader.DEFAULT_WINDOW_KEYWORDS Functions --------- .. autoapisummary:: voxcity.importer.loader.group_material_name voxcity.importer.loader.load_obj_groups voxcity.importer.loader.classify_roles voxcity.importer.loader.select_groups_by_role voxcity.importer.loader.select_building_groups Module Contents --------------- .. py:data:: DEFAULT_WINDOW_KEYWORDS :value: ('window', 'glass', 'glazing') .. py:function:: group_material_name(mesh) -> Optional[str] Return the group's assigned OBJ material name, or None. trimesh exposes the material name on TextureVisuals as ``mesh.visual.material.name``. ColorVisuals / untextured meshes (or any missing attribute) yield None so callers fall back to name-only matching. .. py:function:: load_obj_groups(obj_path, swap_yz: bool = False) -> List[Tuple[str, trimesh.Trimesh]] Load *obj_path* and return its geometry as a list of (name, mesh). :param obj_path: path to an OBJ file. :param swap_yz: if True, return copies of each mesh with axes 1 and 2 (Y/Z) swapped, to reconcile Rhino's Z-up convention with Y-up OBJ exporters. The originally loaded meshes are never mutated. :returns: List of ``(name, mesh)`` tuples, in the order the geometry was discovered. trimesh collapses an OBJ to a single, unnamed ``Trimesh`` (rather than a ``Scene``) whenever the file resolves to only one geometry group — this includes both OBJs with no named scene structure at all *and* OBJs containing exactly one named ``o `` block, since trimesh discards that name in the single-group case. In that situation named parts are recovered first from ``g `` group directives (which trimesh otherwise ignores, e.g. ``g building`` / ``g window``), then by splitting on material (so e.g. a ``Glass`` material becomes its own window-detectable group); if neither yields 2+ groups, a single group named ``"imported_building_1"`` is returned. :raises FileNotFoundError: if *obj_path* does not exist or is not a file (e.g. a directory path). :raises ValueError: if the file loads but contains no usable mesh geometry. .. py:function:: classify_roles(names: Iterable[str], roles: Optional[Dict[str, str]] = None, *, auto_window: bool = True, window_keywords=DEFAULT_WINDOW_KEYWORDS, material_names: Optional[Dict[str, Optional[str]]] = None) -> Dict[str, str] Map each group name to its import role, defaulting to ``"building"``. Resolution order per name: 1. explicit ``roles[name]`` if present (overrides everything), 2. else ``"window"`` if ``auto_window`` and the group name OR its material name (from ``material_names``) contains a window keyword (case-insensitive substring), 3. else ``"building"``. Matching against *roles* is exact string match only. ``material_names`` is an optional ``{name: material_name}`` mapping; absent/None material names simply skip the material-based check. .. py:function:: select_groups_by_role(groups: List[Tuple[str, trimesh.Trimesh]], roles: Optional[Dict[str, str]] = None, *, auto_window: bool = True, window_keywords=DEFAULT_WINDOW_KEYWORDS) -> Dict[str, List[Tuple[str, trimesh.Trimesh]]] Bucket ``(name, mesh)`` groups by resolved role. Returns ``{"building": [...], "window": [...]}``. Material names are read from each mesh via :func:`group_material_name`, so window detection by name or material both work. Any other/unknown role (e.g. ``"skip"``) is dropped and logged at INFO. .. py:function:: select_building_groups(groups: List[Tuple[str, trimesh.Trimesh]], roles: Optional[Dict[str, str]] = None) -> List[Tuple[str, trimesh.Trimesh]] Return only the building-role groups (back-compat wrapper). With auto window detection on by default, groups whose name or material marks them as windows are excluded here (they belong to the ``"window"`` bucket of :func:`select_groups_by_role`).