mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
bim.select_linked_model_element
Refactored methods for accessing objects in linked models and added a simple operator to select object in linked model by providing guid. A quick demo - https://files.catbox.moe/sjjw37.mp4
This commit is contained in:
@@ -66,6 +66,7 @@ classes = (
|
|||||||
operator.RewindLibrary,
|
operator.RewindLibrary,
|
||||||
operator.SaveLibraryFile,
|
operator.SaveLibraryFile,
|
||||||
operator.SelectLibraryFile,
|
operator.SelectLibraryFile,
|
||||||
|
operator.SelectLinkedModelElement,
|
||||||
operator.SelectLinkHandle,
|
operator.SelectLinkHandle,
|
||||||
operator.ToggleFilterCategories,
|
operator.ToggleFilterCategories,
|
||||||
operator.ToggleLinkSelectability,
|
operator.ToggleLinkSelectability,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import traceback
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import radians
|
from math import radians
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Literal, Union, get_args
|
from typing import TYPE_CHECKING, Literal, Union, get_args
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -49,6 +49,10 @@ import ifcopenshell.util.unit
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||||
|
from bpy_extras.view3d_utils import (
|
||||||
|
region_2d_to_origin_3d,
|
||||||
|
region_2d_to_vector_3d,
|
||||||
|
)
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
|
|
||||||
import bonsai.bim.handler
|
import bonsai.bim.handler
|
||||||
@@ -1797,6 +1801,43 @@ class SelectLinkHandle(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class SelectLinkedModelElement(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.select_linked_model_element"
|
||||||
|
bl_label = "Select Linked Model Element"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
bl_description = "Select an element in the currently selected linked model by providing GlobalId."
|
||||||
|
|
||||||
|
guid: bpy.props.StringProperty(name="GlobalId") # pyright: ignore[reportRedeclaration]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
guid: str
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
assert context.window_manager
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
|
guid = self.guid.strip()
|
||||||
|
if not guid:
|
||||||
|
self.report({"ERROR"}, "GlobalId is not provided.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
props = tool.Project.get_project_props()
|
||||||
|
active_link = props.active_link
|
||||||
|
assert active_link is not None
|
||||||
|
assert active_link.is_loaded
|
||||||
|
|
||||||
|
guid_obj = tool.Project.Link.get_obj_by_guid(active_link, guid)
|
||||||
|
if not guid_obj:
|
||||||
|
filepath = active_link.filepath
|
||||||
|
self.report({"INFO"}, f"Element with GlobalId '{guid}' not found in the linked model at '{filepath}'.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
tool.Project.Link.select_linked_element(context, guid_obj, guid)
|
||||||
|
self.report({"INFO"}, f"Element with GlobalId '{guid}' is selected.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ExportIFC(bpy.types.Operator, ExportHelper):
|
class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||||
bl_idname = "bim.save_project"
|
bl_idname = "bim.save_project"
|
||||||
bl_label = "Save IFC"
|
bl_label = "Save IFC"
|
||||||
@@ -2278,21 +2319,10 @@ class QueryLinkedElement(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
assert context.area
|
||||||
return context.area.type == "VIEW_3D"
|
return context.area.type == "VIEW_3D"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from bpy_extras.view3d_utils import (
|
|
||||||
region_2d_to_origin_3d,
|
|
||||||
region_2d_to_vector_3d,
|
|
||||||
)
|
|
||||||
from ifcpatch.recipes.ExtractPropertiesToSQLite import (
|
|
||||||
ElementRow,
|
|
||||||
PropertyRow,
|
|
||||||
RelationshipRow,
|
|
||||||
)
|
|
||||||
|
|
||||||
LinksData.linked_data = {}
|
LinksData.linked_data = {}
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
props.queried_obj = None
|
props.queried_obj = None
|
||||||
@@ -2319,105 +2349,18 @@ class QueryLinkedElement(bpy.types.Operator):
|
|||||||
self.report({"INFO"}, "No object found.")
|
self.report({"INFO"}, "No object found.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
if "guids" not in obj:
|
if not tool.Project.Link.is_linked_element(obj):
|
||||||
self.report({"INFO"}, "Object is not a linked IFC element.")
|
self.report({"INFO"}, "Object is not a linked IFC element.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
guid = None
|
guid = tool.Project.Link.get_guid_by_face_index(obj, face_index)
|
||||||
guid_start_index = 0
|
|
||||||
guid_ids: list[int] = obj["guid_ids"]
|
|
||||||
for i, guid_end_index in enumerate(guid_ids):
|
|
||||||
if face_index < guid_end_index:
|
|
||||||
guid = obj["guids"][i]
|
|
||||||
props.queried_obj = obj
|
|
||||||
props.queried_obj_root = self.find_obj_root(obj, instance_matrix)
|
|
||||||
|
|
||||||
selected_tris: list[tuple[int, ...]] = []
|
|
||||||
selected_edges: list[tuple[int, ...]] = []
|
|
||||||
vert_indices_set: set[int] = set()
|
|
||||||
assert isinstance(obj.data, bpy.types.Mesh)
|
|
||||||
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
|
|
||||||
vert_indices_set.update(polygon.vertices)
|
|
||||||
vert_indices = list(vert_indices_set)
|
|
||||||
vert_map = {k: v for v, k in enumerate(vert_indices)}
|
|
||||||
selected_vertices = [tuple(obj.matrix_world @ obj.data.vertices[vi].co) for vi in vert_indices]
|
|
||||||
for polygon in obj.data.polygons[guid_start_index:guid_end_index]:
|
|
||||||
selected_tris.append(tuple(vert_map[v] for v in polygon.vertices))
|
|
||||||
selected_edges.extend(tuple([vert_map[vi] for vi in e] for e in polygon.edge_keys))
|
|
||||||
|
|
||||||
obj["selected_vertices"] = selected_vertices
|
|
||||||
obj["selected_edges"] = selected_edges
|
|
||||||
obj["selected_tris"] = selected_tris
|
|
||||||
|
|
||||||
break
|
|
||||||
guid_start_index = guid_end_index
|
|
||||||
|
|
||||||
assert guid is not None
|
assert guid is not None
|
||||||
self.db = sqlite3.connect(obj["db"])
|
tool.Project.Link.select_linked_element(context, obj, guid)
|
||||||
self.c = self.db.cursor()
|
|
||||||
|
|
||||||
self.c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1")
|
|
||||||
element = ElementRow(*self.c.fetchone())
|
|
||||||
|
|
||||||
attributes: dict[str, Any] = {}
|
|
||||||
for i, attr in enumerate(["GlobalId", "IFC Class", "Predefined Type", "Name", "Description"]):
|
|
||||||
if element[i + 1] is not None:
|
|
||||||
attributes[attr] = element[i + 1]
|
|
||||||
|
|
||||||
self.c.execute("SELECT * FROM properties WHERE element_id = ?", (element[0],))
|
|
||||||
rows = [PropertyRow(*row) for row in self.c.fetchall()]
|
|
||||||
|
|
||||||
properties: defaultdict[str, dict[str, str]] = defaultdict(dict)
|
|
||||||
for row in rows:
|
|
||||||
properties[row.pset_name][row.name] = row.value
|
|
||||||
|
|
||||||
self.c.execute("SELECT * FROM relationships WHERE from_id = ?", (element[0],))
|
|
||||||
relationships = [RelationshipRow(*row) for row in self.c.fetchall()]
|
|
||||||
|
|
||||||
relating_type_id = None
|
|
||||||
|
|
||||||
for relationship in relationships:
|
|
||||||
if relationship[1] == "IfcRelDefinesByType":
|
|
||||||
relating_type_id = relationship[2]
|
|
||||||
|
|
||||||
type_properties: defaultdict[str, dict[str, str]] = defaultdict(dict)
|
|
||||||
if relating_type_id is not None:
|
|
||||||
self.c.execute("SELECT * FROM properties WHERE element_id = ?", (relating_type_id,))
|
|
||||||
rows = [PropertyRow(*row) for row in self.c.fetchall()]
|
|
||||||
for row in rows:
|
|
||||||
type_properties[row.pset_name][row.name] = row.value
|
|
||||||
|
|
||||||
LinksData.linked_data = {
|
|
||||||
"attributes": attributes,
|
|
||||||
"properties": [(k, properties[k]) for k in sorted(properties.keys())],
|
|
||||||
"type_properties": [(k, type_properties[k]) for k in sorted(type_properties.keys())],
|
|
||||||
}
|
|
||||||
self.db.close()
|
|
||||||
|
|
||||||
for area in bpy.context.screen.areas:
|
|
||||||
if area.type == "PROPERTIES":
|
|
||||||
for region in area.regions:
|
|
||||||
if region.type == "WINDOW":
|
|
||||||
region.tag_redraw()
|
|
||||||
elif area.type == "VIEW_3D":
|
|
||||||
area.tag_redraw()
|
|
||||||
|
|
||||||
self.report({"INFO"}, f"Loaded data for {guid}")
|
self.report({"INFO"}, f"Loaded data for {guid}")
|
||||||
ProjectDecorator.install(bpy.context)
|
ProjectDecorator.install(bpy.context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def find_obj_root(self, obj: bpy.types.Object, matrix: Matrix) -> Union[bpy.types.Object, None]:
|
|
||||||
collections = set(obj.users_collection)
|
|
||||||
for o in bpy.data.objects:
|
|
||||||
if (
|
|
||||||
o.type != "EMPTY"
|
|
||||||
or o.instance_type != "COLLECTION"
|
|
||||||
or o.instance_collection not in collections
|
|
||||||
or not np.allclose(matrix, o.matrix_world, atol=1e-4)
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
return o
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.mouse_x = event.mouse_region_x
|
self.mouse_x = event.mouse_region_x
|
||||||
self.mouse_y = event.mouse_region_y
|
self.mouse_y = event.mouse_region_y
|
||||||
@@ -2702,11 +2645,6 @@ class CreateClippingPlane(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
from bpy_extras.view3d_utils import (
|
|
||||||
region_2d_to_origin_3d,
|
|
||||||
region_2d_to_vector_3d,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clean up deleted planes
|
# Clean up deleted planes
|
||||||
props = tool.Project.get_project_props()
|
props = tool.Project.get_project_props()
|
||||||
if len(props.clipping_planes) > 5:
|
if len(props.clipping_planes) > 5:
|
||||||
|
|||||||
@@ -490,6 +490,7 @@ class BIM_PT_links(Panel):
|
|||||||
row.operator("bim.disable_editing_link", text="", icon="CANCEL")
|
row.operator("bim.disable_editing_link", text="", icon="CANCEL")
|
||||||
else:
|
else:
|
||||||
row.operator("bim.enable_editing_link", text="", icon="GREASEPENCIL")
|
row.operator("bim.enable_editing_link", text="", icon="GREASEPENCIL")
|
||||||
|
row.operator("bim.select_linked_model_element", icon="VIEWZOOM", text="")
|
||||||
row.operator("bim.select_link_handle", text="", icon="OBJECT_DATA").link_index = index
|
row.operator("bim.select_link_handle", text="", icon="OBJECT_DATA").link_index = index
|
||||||
row.operator("bim.unload_link", text="", icon="UNLINKED").link_index = index
|
row.operator("bim.unload_link", text="", icon="UNLINKED").link_index = index
|
||||||
row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index
|
row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import shutil
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import radians
|
from math import radians
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, NamedTuple, Optional
|
from typing import TYPE_CHECKING, Any, NamedTuple, Optional
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -34,6 +34,7 @@ import ifcopenshell.util.representation
|
|||||||
import ifcopenshell.util.shape_builder
|
import ifcopenshell.util.shape_builder
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from ifcopenshell.api.project.append_asset import APPENDABLE_ASSET_TYPES
|
from ifcopenshell.api.project.append_asset import APPENDABLE_ASSET_TYPES
|
||||||
|
from mathutils import Matrix
|
||||||
|
|
||||||
import bonsai.bim.schema
|
import bonsai.bim.schema
|
||||||
import bonsai.core.aggregate
|
import bonsai.core.aggregate
|
||||||
@@ -46,7 +47,11 @@ import bonsai.tool as tool
|
|||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bonsai.bim.module.project.prop import BIMProjectProperties, MeasureToolSettings, Link
|
from bonsai.bim.module.project.prop import (
|
||||||
|
BIMProjectProperties,
|
||||||
|
Link,
|
||||||
|
MeasureToolSettings,
|
||||||
|
)
|
||||||
|
|
||||||
HiearchyDict = dict[ifcopenshell.entity_instance, "HiearchyDict"]
|
HiearchyDict = dict[ifcopenshell.entity_instance, "HiearchyDict"]
|
||||||
|
|
||||||
@@ -497,8 +502,8 @@ class Project(bonsai.core.tool.Project):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_clipping_planes_normals(cls):
|
def get_clipping_planes_normals(cls) -> list[tuple[Vector, Vector]]:
|
||||||
normals = []
|
normals: list[tuple[Vector, Vector]] = []
|
||||||
for clipping_plane in tool.Project.get_project_props().clipping_planes:
|
for clipping_plane in tool.Project.get_project_props().clipping_planes:
|
||||||
plane = clipping_plane.obj
|
plane = clipping_plane.obj
|
||||||
if not plane or not plane.data:
|
if not plane or not plane.data:
|
||||||
@@ -507,6 +512,7 @@ class Project(bonsai.core.tool.Project):
|
|||||||
if plane.mode == "EDIT":
|
if plane.mode == "EDIT":
|
||||||
continue # A profile decorator or something else is used here.
|
continue # A profile decorator or something else is used here.
|
||||||
|
|
||||||
|
assert isinstance(plane.data, bpy.types.Mesh)
|
||||||
v1 = plane.matrix_world @ plane.data.vertices[0].co
|
v1 = plane.matrix_world @ plane.data.vertices[0].co
|
||||||
v2 = plane.matrix_world @ plane.data.vertices[1].co
|
v2 = plane.matrix_world @ plane.data.vertices[1].co
|
||||||
v3 = plane.matrix_world @ plane.data.vertices[2].co
|
v3 = plane.matrix_world @ plane.data.vertices[2].co
|
||||||
@@ -589,3 +595,163 @@ class Project(bonsai.core.tool.Project):
|
|||||||
|
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
ifcopenshell.api.document.remove_information(ifc_file, information=doc)
|
ifcopenshell.api.document.remove_information(ifc_file, information=doc)
|
||||||
|
|
||||||
|
class Link:
|
||||||
|
"""Tools for working with linked models."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_linked_element(cls, obj: bpy.types.Object) -> bool:
|
||||||
|
return "guids" in obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_obj_by_guid(cls, link: Link, guid: str) -> bpy.types.Object | None:
|
||||||
|
assert link.is_loaded
|
||||||
|
|
||||||
|
handle = tool.Project.get_link_empty_handle(link)
|
||||||
|
assert handle
|
||||||
|
col = handle.instance_collection
|
||||||
|
assert col
|
||||||
|
|
||||||
|
guid_obj = None
|
||||||
|
for obj in col.objects:
|
||||||
|
obj_guids: list[str] = obj["guids"]
|
||||||
|
if guid in obj_guids:
|
||||||
|
guid_obj = obj
|
||||||
|
break
|
||||||
|
|
||||||
|
return guid_obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_guid_by_face_index(cls, obj: bpy.types.Object, face_index: int) -> str | None:
|
||||||
|
guids: list[str] = obj["guids"]
|
||||||
|
guid_ids: list[int] = obj["guid_ids"]
|
||||||
|
for guid, guid_end_index in zip(guids, guid_ids):
|
||||||
|
if face_index < guid_end_index:
|
||||||
|
return guid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def select_linked_element_geom(cls, obj: bpy.types.Object, guid: str) -> None:
|
||||||
|
mesh = obj.data
|
||||||
|
assert isinstance(mesh, bpy.types.Mesh)
|
||||||
|
obj_guids: list[str] = obj["guids"]
|
||||||
|
obj_guid_ids: list[int] = obj["guid_ids"]
|
||||||
|
|
||||||
|
index = obj_guids.index(guid)
|
||||||
|
guid_end_index = obj_guid_ids[index]
|
||||||
|
if index > 0:
|
||||||
|
guid_start_index = obj_guid_ids[index - 1]
|
||||||
|
else:
|
||||||
|
guid_start_index = 0
|
||||||
|
guid_polygons = mesh.polygons[guid_start_index:guid_end_index]
|
||||||
|
|
||||||
|
selected_tris: list[tuple[int, ...]] = []
|
||||||
|
selected_edges: list[tuple[int, ...]] = []
|
||||||
|
|
||||||
|
# Restart verts indices for our polygons.
|
||||||
|
guid_vertices_set: set[int] = set()
|
||||||
|
for polygon in guid_polygons:
|
||||||
|
guid_vertices_set.update(polygon.vertices)
|
||||||
|
vert_map = {k: v for v, k in enumerate(guid_vertices_set)}
|
||||||
|
|
||||||
|
selected_vertices = [obj.matrix_world @ mesh.vertices[vi].co for vi in vert_map]
|
||||||
|
for polygon in guid_polygons:
|
||||||
|
selected_tris.append(tuple(vert_map[vi] for vi in polygon.vertices))
|
||||||
|
selected_edges.extend(tuple([vert_map[vi] for vi in e]) for e in polygon.edge_keys)
|
||||||
|
|
||||||
|
obj["selected_vertices"] = selected_vertices
|
||||||
|
obj["selected_edges"] = selected_edges
|
||||||
|
obj["selected_tris"] = selected_tris
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def select_linked_element(
|
||||||
|
cls,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
obj: bpy.types.Object,
|
||||||
|
guid: str,
|
||||||
|
instance_matrix: Matrix | None = None,
|
||||||
|
) -> None:
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from ifcpatch.recipes.ExtractPropertiesToSQLite import (
|
||||||
|
ElementRow,
|
||||||
|
PropertyRow,
|
||||||
|
RelationshipRow,
|
||||||
|
)
|
||||||
|
|
||||||
|
from bonsai.bim.module.project.data import LinksData
|
||||||
|
from bonsai.bim.module.project.decorator import ProjectDecorator
|
||||||
|
|
||||||
|
# Not sure if there's a difference between `instance_matrix` coming from `ray_cast`
|
||||||
|
# and usual `matrix_world`, maybe we can just get it from object always.
|
||||||
|
if instance_matrix is None:
|
||||||
|
instance_matrix = obj.matrix_world
|
||||||
|
|
||||||
|
props = tool.Project.get_project_props()
|
||||||
|
props.queried_obj = obj
|
||||||
|
props.queried_obj_root = cls.find_obj_root(obj, instance_matrix)
|
||||||
|
|
||||||
|
cls.select_linked_element_geom(obj, guid)
|
||||||
|
db = sqlite3.connect(obj["db"])
|
||||||
|
c = db.cursor()
|
||||||
|
|
||||||
|
c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1")
|
||||||
|
element = ElementRow(*c.fetchone())
|
||||||
|
|
||||||
|
attributes: dict[str, Any] = {}
|
||||||
|
for i, attr in enumerate(["GlobalId", "IFC Class", "Predefined Type", "Name", "Description"]):
|
||||||
|
if element[i + 1] is not None:
|
||||||
|
attributes[attr] = element[i + 1]
|
||||||
|
|
||||||
|
c.execute("SELECT * FROM properties WHERE element_id = ?", (element[0],))
|
||||||
|
rows = [PropertyRow(*row) for row in c.fetchall()]
|
||||||
|
|
||||||
|
properties: defaultdict[str, dict[str, str]] = defaultdict(dict)
|
||||||
|
for row in rows:
|
||||||
|
properties[row.pset_name][row.name] = row.value
|
||||||
|
|
||||||
|
c.execute("SELECT * FROM relationships WHERE from_id = ?", (element[0],))
|
||||||
|
relationships = [RelationshipRow(*row) for row in c.fetchall()]
|
||||||
|
|
||||||
|
relating_type_id = None
|
||||||
|
|
||||||
|
for relationship in relationships:
|
||||||
|
if relationship[1] == "IfcRelDefinesByType":
|
||||||
|
relating_type_id = relationship[2]
|
||||||
|
|
||||||
|
type_properties: defaultdict[str, dict[str, str]] = defaultdict(dict)
|
||||||
|
if relating_type_id is not None:
|
||||||
|
c.execute("SELECT * FROM properties WHERE element_id = ?", (relating_type_id,))
|
||||||
|
rows = [PropertyRow(*row) for row in c.fetchall()]
|
||||||
|
for row in rows:
|
||||||
|
type_properties[row.pset_name][row.name] = row.value
|
||||||
|
|
||||||
|
LinksData.linked_data = {
|
||||||
|
"attributes": attributes,
|
||||||
|
"properties": [(k, properties[k]) for k in sorted(properties.keys())],
|
||||||
|
"type_properties": [(k, type_properties[k]) for k in sorted(type_properties.keys())],
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
assert context.screen
|
||||||
|
for area in context.screen.areas:
|
||||||
|
if area.type == "PROPERTIES":
|
||||||
|
for region in area.regions:
|
||||||
|
if region.type == "WINDOW":
|
||||||
|
region.tag_redraw()
|
||||||
|
elif area.type == "VIEW_3D":
|
||||||
|
area.tag_redraw()
|
||||||
|
|
||||||
|
ProjectDecorator.install(context)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_obj_root(cls, obj: bpy.types.Object, matrix: Matrix) -> bpy.types.Object | None:
|
||||||
|
collections = set(obj.users_collection)
|
||||||
|
for o in bpy.data.objects:
|
||||||
|
if (
|
||||||
|
o.type != "EMPTY"
|
||||||
|
or o.instance_type != "COLLECTION"
|
||||||
|
or o.instance_collection not in collections
|
||||||
|
or not np.allclose(matrix, o.matrix_world, atol=1e-4)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
return o
|
||||||
|
|||||||
Reference in New Issue
Block a user