mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
Linked Models - option to isolate selected object
Quick demo - https://files.catbox.moe/6chdx7.mp4
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user