Linked Models - option to isolate selected object

Quick demo - https://files.catbox.moe/6chdx7.mp4
This commit is contained in:
Andrej730
2026-03-12 18:59:02 +05:00
parent 82951fe702
commit 8bd3fab608
3 changed files with 127 additions and 8 deletions
@@ -2373,19 +2373,23 @@ class HideQueriedLinkedElement(bpy.types.Operator):
bl_label = "Hide Queried Linked Element"
bl_description = (
"Hide geometry for currently queried linked element.\n\n"
"ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n"
"(Not Yet Implemented) SHIFT+Click to hide everything but currently queried element.\n\n"
"Know limitation: doesn't work with UNDO."
"SHIFT+Click (or SHIFT+H in Explore Tool) to hide everything "
"in the currently selected model, but the queried element.\n"
"ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n\n"
"Known limitation: doesn't work with UNDO."
)
bl_options = {"REGISTER", "UNDO"}
unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
unhide_all: bool
hide_all_except: bool
def invoke(self, context, event):
self.unhide_all = event.alt
self.hide_all_except = event.shift
return self.execute(context)
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
@@ -2394,6 +2398,9 @@ class HideQueriedLinkedElement(bpy.types.Operator):
if self.unhide_all:
return self.run_unhide_all()
if self.hide_all_except:
return self.run_hide_all_except()
obj = props.queried_obj
if not obj:
self.report({"INFO"}, "No object is queried to hide.")
@@ -2415,6 +2422,21 @@ class HideQueriedLinkedElement(bpy.types.Operator):
self.report({"INFO"}, "All linked model geometry is unhidden.")
return {"FINISHED"}
def run_hide_all_except(self) -> set["rna_enums.OperatorReturnItems"]:
props = tool.Project.get_project_props()
obj = props.queried_obj
if not obj:
self.report({"INFO"}, "No object is queried.")
return {"FINISHED"}
link = props.active_link
if not link:
self.report({"INFO"}, "No linked model is currently selected.")
return {"FINISHED"}
guid = props.queried_guid
tool.Project.Link.hide_all_elements_except(link, obj, guid)
self.report({"INFO"}, "All other linked model geometry is now hidden.")
return {"FINISHED"}
class AppendInspectedLinkedElement(AppendLibraryElement):
bl_idname = "bim.append_inspected_linked_element"
@@ -41,6 +41,7 @@ class ExploreTool(bpy.types.WorkSpaceTool):
("bim.explore_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
("bim.explore_hotkey", {"type": "S", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_S")]}),
("bim.explore_hotkey", {"type": "H", "value": "PRESS"}, {"properties": [("hotkey", "H")]}),
("bim.explore_hotkey", {"type": "H", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_H")]}),
("bim.explore_hotkey", {"type": "H", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_H")]}),
)
@@ -147,5 +148,8 @@ class ExploreHotkey(bpy.types.Operator):
def hotkey_H(self) -> None:
bpy.ops.bim.hide_queried_linked_element()
def hotkey_S_H(self) -> None:
bpy.ops.bim.hide_queried_linked_element(hide_all_except=True)
def hotkey_A_H(self) -> None:
bpy.ops.bim.hide_queried_linked_element(unhide_all=True)
+98 -5
View File
@@ -24,7 +24,15 @@ import shutil
from collections import defaultdict
from math import radians
from pathlib import Path
from typing import TYPE_CHECKING, Any, NamedTuple, Optional
from typing import (
TYPE_CHECKING,
Any,
Literal,
NamedTuple,
NotRequired,
Optional,
TypedDict,
)
import bpy
import ifcopenshell
@@ -600,6 +608,49 @@ class Project(bonsai.core.tool.Project):
class Link:
"""Tools for working with linked models."""
class LinkedObjectChunk(TypedDict):
"""There's actually no dictionary with those keys,
just using this class to document what keys we do assign to the objects
that represent chunks of the linked models.
"""
guids: list[str]
"""List of guids present in the object."""
guid_ids: list[int]
"""Number of faces that belong to each guid.
E.g. if chunk consists of two 12 tris cubes:
```
guids = ["aaa", "bbb"]
# Meaning object has 24 polygons
# [0;11] is part of "aaa", [12:23] is part of "bbb".
guid_ids = [12, 24]
```
"""
db: str
"""Absolute filepath to .ifc.cache.sqlite."""
ifc_filepath: str
"""Absolute filepath to .ifc."""
# Only added when object is queried.
selected_vertices: NotRequired[list[tuple[int, int, int]]]
selected_edges: NotRequired[list[tuple[int, int]]]
selected_tris: NotRequired[list[tuple[int, int, int]]]
hidden_indices: NotRequired[list[int]]
"""List of hidden indices in "guids".
Note that entire object also can be hidden by ``hide_viewport`` and then "hidden_indices" won't be set.
```
guids = ["aaa", "bbb", "ccc"]
# guid "bbb" is hidden.
hidden_indices = [1]
```
"""
@classmethod
def is_linked_element(cls, obj: bpy.types.Object) -> bool:
return "guids" in obj
@@ -670,9 +721,11 @@ class Project(bonsai.core.tool.Project):
return slice(guid_start_index, guid_end_index)
@classmethod
def hide_linked_element(cls, obj: bpy.types.Object, guid: str) -> None:
verts = tool.Project.Link.get_linked_element_verts(obj, guid)
def setup_hide_modifier(
cls,
obj: bpy.types.Object,
hide_type: Literal["hide_selected", "hide_unselected"],
) -> bpy.types.VertexGroup:
# `MeshPolygon.hide` works only in EDIT mode,
# so we use vertex groups + Mask modifier.
# But since for hiding we're modifying object in a linked model,
@@ -692,8 +745,15 @@ class Project(bonsai.core.tool.Project):
modifier = modifiers.new(MODIFIER_VG_NAME, "MASK")
assert isinstance(modifier, bpy.types.MaskModifier)
modifier.vertex_group = MODIFIER_VG_NAME
modifier.invert_vertex_group = True
# Mask modifier by default shows only geometry from the provided vertex group.
modifier.invert_vertex_group = hide_type == "hide_selected"
return vertex_group
@classmethod
def hide_linked_element(cls, obj: bpy.types.Object, guid: str) -> None:
verts = tool.Project.Link.get_linked_element_verts(obj, guid)
vertex_group = cls.setup_hide_modifier(obj, "hide_selected")
vertex_group.add(verts, 1.0, "REPLACE")
hidden_indices: list[int] = list(obj.get("hidden_indices") or [])
@@ -710,12 +770,45 @@ class Project(bonsai.core.tool.Project):
assert col
for obj_ in col.objects:
obj_.hide_viewport = False
if "hidden_indices" not in obj_:
continue
obj_.vertex_groups.clear()
obj_.modifiers.clear()
del obj_["hidden_indices"]
@classmethod
def hide_all_elements_except(cls, link: Link, queried_obj: bpy.types.Object, queried_guid: str) -> None:
handle = tool.Project.get_link_empty_handle(link)
assert handle
col = handle.instance_collection
assert col
for obj_ in col.objects:
if "guids" not in obj_:
continue
if obj_ != queried_obj:
# Just hide the entire chunk, if queried guid is not part of it.
obj_.hide_viewport = True
continue
guids: list[str] = obj_["guids"]
queried_guid_index = guids.index(queried_guid)
# Get vertices for the queried element before modifying hidden_indices.
queried_verts = cls.get_linked_element_verts(obj_, queried_guid)
vertex_group = cls.setup_hide_modifier(obj_, "hide_unselected")
assert isinstance(mesh := obj_.data, bpy.types.Mesh)
# Clean up possible previously hidden elements.
vertex_group.remove(range(len(mesh.vertices)))
vertex_group.add(queried_verts, 1.0, "REPLACE")
# Mark all guids as hidden except the queried one.
obj_["hidden_indices"] = [i for i in range(len(guids)) if i != queried_guid_index]
@classmethod
def select_linked_element_geom(cls, obj: bpy.types.Object, guid: str) -> None:
slice_ = cls.get_linked_element_geom_slice(obj, guid)