This commit is contained in:
Andrej730
2025-02-24 11:37:19 +05:00
parent 0783f8082e
commit 6eda4e389c
34 changed files with 336 additions and 245 deletions
+2 -1
View File
@@ -1076,9 +1076,10 @@ class IfcImporter:
print(traceback.format_exc())
def set_default_context(self):
rprops = tool.Root.get_root_props()
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
rprops.contexts = str(subcontext.id())
break
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
+1 -1
View File
@@ -97,7 +97,7 @@ class AuthoringData:
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
@@ -58,7 +58,8 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
def execute(self, context):
obj = bpy.data.objects.new("TYPEX", None)
context.scene.collection.objects.link(obj)
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
rprops = tool.Root.get_root_props()
rprops.ifc_product = "IfcElementType"
tool.Blender.select_and_activate_single_object(context, obj)
return {"FINISHED"}
@@ -71,7 +72,7 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
ifc_element_type: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
props.ifc_product = "IfcElementType"
props.ifc_class = self.ifc_element_type
if self.ifc_element_type == "IfcWallType":
@@ -363,7 +364,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
)
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
rprops = context.scene.BIMRootProperties
rprops = tool.Root.get_root_props()
ifc_context = None
if get_enum_items(rprops, "contexts", context):
ifc_context = int(rprops.contexts or "0") or None
+6 -3
View File
@@ -60,7 +60,8 @@ class IfcClassData:
@classmethod
def ifc_classes(cls):
ifc_product = bpy.context.scene.BIMRootProperties.ifc_product
rprops = tool.Root.get_root_props()
ifc_product = rprops.ifc_product
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
@@ -80,7 +81,8 @@ class IfcClassData:
@classmethod
def ifc_predefined_types(cls):
types_enum = []
ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
rprops = tool.Root.get_root_props()
ifc_class = rprops.ifc_class
declaration = tool.Ifc.schema().declaration_by_name(ifc_class)
version = tool.Ifc.get_schema()
for attribute in declaration.attributes():
@@ -114,7 +116,8 @@ class IfcClassData:
@classmethod
def representation_template(cls):
ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
rprops = tool.Root.get_root_props()
ifc_class = rprops.ifc_class
templates = [
("EMPTY", "No Geometry", "Start with an empty object"),
None,
+15 -14
View File
@@ -42,6 +42,7 @@ class EnableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
rprops = tool.Root.get_root_props()
obj = context.active_object
self.file = tool.Ifc.get()
element = tool.Ifc.get_entity(obj)
@@ -53,7 +54,7 @@ class EnableReassignClass(bpy.types.Operator):
declaration = schema.declaration_by_name(ifc_class)
for ifc_product in ifc_products:
if ifcopenshell.util.schema.is_a(declaration, ifc_product):
context.scene.BIMRootProperties.ifc_product = ifc_product
rprops.ifc_product = ifc_product
break
else:
self.report({"ERROR"}, f"Couldn't find matching IFC product for the selected object: '{element}'.")
@@ -61,13 +62,13 @@ class EnableReassignClass(bpy.types.Operator):
return {"CANCELLED"}
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context.scene.BIMRootProperties.ifc_class = element.is_a()
context.scene.BIMRootProperties.relating_class_object = None
rprops.ifc_class = element.is_a()
rprops.relating_class_object = None
if hasattr(element, "PredefinedType"):
if element.PredefinedType:
context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
rprops.ifc_predefined_type = element.PredefinedType
userdefined_type = ifcopenshell.util.element.get_predefined_type(element)
context.scene.BIMRootProperties.ifc_userdefined_type = userdefined_type or ""
rprops.ifc_userdefined_type = userdefined_type or ""
return {"FINISHED"}
@@ -94,9 +95,9 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
else:
objects = set(context.selected_objects + [context.active_object])
self.file = tool.Ifc.get()
root_props = context.scene.BIMRootProperties
ifc_product: str = root_props.ifc_product
ifc_class: str = root_props.ifc_class
root_props = tool.Root.get_root_props()
ifc_product = root_props.ifc_product
ifc_class = root_props.ifc_class
type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema)), None)
predefined_type = root_props.ifc_predefined_type
@@ -176,7 +177,7 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
ifc_representation_class: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
objects: list[bpy.types.Object] = []
if self.obj:
objects = [bpy.data.objects[self.obj]]
@@ -358,7 +359,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
return IfcStore.execute_ifc_operator(self, context, event, method="INVOKE")
def _invoke(self, context, event):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
# For convenience, preselect OBJs if applicable
if props.ifc_product == "IfcFeatureElement":
if (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH":
@@ -376,7 +377,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
predefined_type = (
props.ifc_userdefined_type if props.ifc_predefined_type == "USERDEFINED" else props.ifc_predefined_type
)
@@ -500,7 +501,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
else:
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
if representation_template == "PROFILESET":
profile_id = tool.Blender.get_enum_safe(context.scene.BIMRootProperties, "profile")
profile_id = tool.Blender.get_enum_safe(props, "profile")
if profile_id in ("-", None):
profile = next((p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName), None)
if profile is None:
@@ -589,7 +590,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.set_active_object(obj)
def draw(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
self.layout.use_property_split = True
self.layout.use_property_decorate = False
row = self.layout.row()
@@ -600,7 +601,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
if not self.ifc_product:
prop_with_search(self.layout, props, "ifc_product", text="Definition", should_click_ok=True)
prop_with_search(self.layout, props, "ifc_class", should_click_ok=True)
ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
ifc_predefined_types = root_prop.get_ifc_predefined_types(props, context)
if ifc_predefined_types:
prop_with_search(self.layout, props, "ifc_predefined_type", should_click_ok=True)
if props.ifc_predefined_type == "USERDEFINED":
+31 -23
View File
@@ -34,28 +34,27 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Union
def get_ifc_predefined_types(self, context):
def get_ifc_predefined_types(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_predefined_types"]
def get_representation_template(self, context):
def get_representation_template(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["representation_template"]
def refresh_classes(self, context):
old_class = context.scene.BIMRootProperties.ifc_class
old_predefined_type = (
context.scene.BIMRootProperties.ifc_predefined_type if get_ifc_predefined_types(self, context) else ""
)
def refresh_classes(self: "BIMRootProperties", context: bpy.types.Context) -> None:
old_class = self.ifc_class
old_predefined_type = self.ifc_predefined_type if get_ifc_predefined_types(self, context) else ""
enum = get_ifc_classes(self, context)
context.scene.BIMRootProperties.ifc_class = enum[0][0]
self.ifc_class = enum[0][0]
IfcClassData.load()
if self.ifc_product == "IfcFeatureElement":
@@ -81,48 +80,44 @@ def refresh_classes(self, context):
self.ifc_predefined_type = old_predefined_type
def refresh_predefined_types(self, context):
def refresh_predefined_types(self: "BIMRootProperties", context: bpy.types.Context) -> None:
IfcClassData.load()
enum = get_ifc_predefined_types(self, context)
if enum:
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
self.ifc_predefined_type = enum[0][0]
def update_class_enum(self, context):
self.ifc_class = self.ifc_class_filter_enum
def get_ifc_products(self, context):
def get_ifc_products(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_products"]
def get_ifc_classes(self, context):
def get_ifc_classes(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_classes"]
def get_ifc_classes_suggestions():
def get_ifc_classes_suggestions(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_classes_suggestions"]
def get_contexts(self, context):
def get_contexts(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["contexts"]
def get_profile(self, context):
def get_profile(self: "BIMRootProperties", context: bpy.types.Context) -> list[tuple[str, str]]:
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["profile"]
def update_relating_class_from_object(self, context):
def update_relating_class_from_object(self: "BIMRootProperties", context: bpy.types.Context) -> None:
if self.relating_class_object is None:
return
element = tool.Ifc.get_entity(self.relating_class_object)
@@ -139,7 +134,7 @@ def update_relating_class_from_object(self, context):
bpy.ops.bim.reassign_class()
def is_object_class_applicable(self, obj):
def is_object_class_applicable(self: "BIMRootProperties", obj: bpy.types.Object) -> bool:
element = tool.Ifc.get_entity(obj)
if not element:
return False
@@ -149,11 +144,11 @@ def is_object_class_applicable(self, obj):
return element.is_a("IfcTypeObject") == active_element.is_a("IfcTypeObject")
def poll_representation_obj(self, obj):
def poll_representation_obj(self: "BIMRootProperties", obj: bpy.types.Object) -> bool:
return obj.type == "MESH" and obj.data.polygons
def poll_featured_obj(self, obj):
def poll_featured_obj(self: "BIMRootProperties", obj: bpy.types.Object) -> bool:
return tool.Ifc.get_entity(obj)
@@ -192,3 +187,16 @@ class BIMRootProperties(PropertyGroup):
getter_enum_suggestions = {
"ifc_class": get_ifc_classes_suggestions,
}
if TYPE_CHECKING:
contexts: str
description: str
ifc_product: str
ifc_class: str
ifc_predefined_type: str
ifc_userdefined_type: str
featured_obj: Union[bpy.types.Object, None]
representation_template: str
representation_obj: Union[bpy.types.Object, None]
profile: str
relating_class_object: Union[bpy.types.Object, None]
+8 -7
View File
@@ -44,6 +44,7 @@ class BIM_PT_class(Panel):
if not IfcClassData.is_loaded:
IfcClassData.load()
props = context.active_object.BIMObjectProperties
rprops = tool.Root.get_root_props()
if props.ifc_definition_id:
if not IfcClassData.data["has_entity"]:
row = self.layout.row(align=True)
@@ -58,10 +59,10 @@ class BIM_PT_class(Panel):
row.operator("bim.disable_reassign_class", icon="CANCEL", text="")
self.draw_class_dropdowns(
context,
root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context),
root_prop.get_ifc_predefined_types(rprops, context),
is_reassigning_class=True,
)
self.layout.prop(context.scene.BIMRootProperties, "relating_class_object", icon="COPYDOWN")
self.layout.prop(rprops, "relating_class_object", icon="COPYDOWN")
else:
row = self.layout.row(align=True)
row.label(
@@ -78,16 +79,16 @@ class BIM_PT_class(Panel):
if AuthoringData.data["is_representation_item_active"]:
return
ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
ifc_predefined_types = root_prop.get_ifc_predefined_types(rprops, context)
self.draw_class_dropdowns(context, ifc_predefined_types)
row = self.layout.row(align=True)
op = row.operator("bim.assign_class")
op.ifc_class = context.scene.BIMRootProperties.ifc_class
op.predefined_type = context.scene.BIMRootProperties.ifc_predefined_type if ifc_predefined_types else ""
op.userdefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
op.ifc_class = rprops.ifc_class
op.predefined_type = rprops.ifc_predefined_type if ifc_predefined_types else ""
op.userdefined_type = rprops.ifc_userdefined_type
def draw_class_dropdowns(self, context, ifc_predefined_types, is_reassigning_class=False):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
layout = self.layout
prop_with_search(layout, props, "ifc_product")
prop_with_search(layout, props, "ifc_class")
+3 -3
View File
@@ -58,7 +58,7 @@ class SpatialData:
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
@@ -102,7 +102,7 @@ class SpatialDecompositionData:
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
@@ -112,7 +112,7 @@ class SpatialDecompositionData:
@classmethod
def subelement_class(cls) -> list[tuple[str, str, str]]:
results = []
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if not (container := props.active_container):
return results
container_class = tool.Ifc.get().by_id(container.ifc_definition_id).is_a()
@@ -66,7 +66,8 @@ class GridDecorator:
blf.size(font_id, 12)
blf.enable(font_id, blf.SHADOW)
for axis in context.scene.BIMGridProperties.grid_axes:
grid_props = tool.Spatial.get_grid_props()
for axis in grid_props.grid_axes:
if not (obj := axis.obj) or obj.hide_get() == True:
continue
if obj.select_get() and context.mode != "OBJECT":
@@ -120,7 +121,8 @@ class GridDecorator:
selected_edges = []
unselected_verts = []
unselected_edges = []
for axis in context.scene.BIMGridProperties.grid_axes:
grid_props = tool.Spatial.get_grid_props()
for axis in grid_props.grid_axes:
if (obj := axis.obj) and obj.hide_get() == False:
if obj.select_get():
if context.mode != "OBJECT":
@@ -96,7 +96,7 @@ class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
container = tool.Ifc.get().by_id(self.container)
elif (
(obj := tool.Blender.get_active_object())
and (props := obj.BIMObjectSpatialProperties)
and (props := tool.Spatial.get_object_spatial_props(obj))
and (container_obj := props.container_obj)
and (container := tool.Ifc.get_entity(container_obj))
):
+53 -4
View File
@@ -36,15 +36,18 @@ import bonsai.core.geometry
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
from typing import TYPE_CHECKING, Union, Literal
def get_subelement_class(self, context):
def get_subelement_class(
self: "BIMSpatialDecompositionProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not SpatialDecompositionData.is_loaded:
SpatialDecompositionData.load()
return SpatialDecompositionData.data["subelement_class"]
def update_elevation(self, context):
def update_elevation(self: "BIMContainer", context: bpy.types.Context) -> None:
try:
elevation = float(self.elevation)
if self.elevation != str(elevation):
@@ -91,7 +94,7 @@ def update_element_mode(self: "BIMSpatialDecompositionProperties", context: bpy.
tool.Spatial.load_contained_elements()
def update_grid_is_locked(self, context):
def update_grid_is_locked(self: "BIMGridProperties", context: bpy.types.Context) -> None:
if not tool.Ifc.get():
return
if tool.Ifc.get().schema in ("IFC2X3", "IFC4"):
@@ -108,7 +111,7 @@ def update_grid_is_locked(self, context):
bonsai.bim.handler.refresh_ui_data()
def update_spatial_is_locked(self, context):
def update_spatial_is_locked(self: "BIMSpatialDecompositionProperties", context: bpy.types.Context) -> None:
if not tool.Ifc.get():
return
if tool.Ifc.get().schema == "IFC2X3":
@@ -150,6 +153,10 @@ class BIMObjectSpatialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
container_obj: PointerProperty(type=bpy.types.Object, name="Container", poll=poll_container_obj)
if TYPE_CHECKING:
is_editing: bool
container_obj: Union[bpy.types.Object, None]
class BIMContainer(PropertyGroup):
name: StringProperty(name="Name", update=update_name)
@@ -162,6 +169,16 @@ class BIMContainer(PropertyGroup):
is_expanded: BoolProperty(name="Is Expanded")
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
ifc_class: str
description: str
long_name: str
elevation: str
level_index: int
has_children: bool
is_expanded: bool
ifc_definition_id: int
class Element(PropertyGroup):
name: StringProperty(name="Name")
@@ -185,6 +202,16 @@ class Element(PropertyGroup):
),
)
if TYPE_CHECKING:
ifc_class: str
identification: str
ifc_definition_id: int
level: int
has_children: bool
total: int
is_expanded: bool
type: Literal["CLASS", "TYPE", "CLASSIFICATION", "OCCURRENCE"]
class BIMSpatialDecompositionProperties(PropertyGroup):
is_locked: BoolProperty(
@@ -223,6 +250,23 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
name="Should Include Children", default=True, update=update_should_include_children
)
if TYPE_CHECKING:
is_locked: bool
is_visible: bool
container_filter: str
containers: bpy.types.bpy_prop_collection_idprop[BIMContainer]
contracted_containers: str
active_container_index: int
element_filter: str
elements: bpy.types.bpy_prop_collection_idprop[Element]
expanded_elements: str
active_element_index: int
total_elements: int
element_mode: Literal["TYPE", "DECOMPOSITION", "CLASSIFICATION"]
subelement_class: str
default_container: int
should_include_children: bool
@property
def active_container(self):
if self.containers and self.active_container_index < len(self.containers):
@@ -248,3 +292,8 @@ class BIMGridProperties(PropertyGroup):
update=update_grid_is_visible,
)
grid_axes: CollectionProperty(name="Grid Axes", type=ObjProperty)
if TYPE_CHECKING:
is_locked: bool
is_visible: bool
grid_axes: bpy.types.bpy_prop_collection_idprop[ObjProperty]
+58 -27
View File
@@ -16,10 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
from bpy.types import Panel, UIList
from bonsai.bim.module.spatial.data import SpatialData, SpatialDecompositionData
import bonsai.tool as tool
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.spatial.prop import BIMSpatialDecompositionProperties, BIMContainer, Element
class BIM_PT_spatial(Panel):
@@ -40,7 +45,9 @@ class BIM_PT_spatial(Panel):
if not SpatialData.is_loaded:
SpatialData.load()
osprops = context.active_object.BIMObjectSpatialProperties
obj = context.active_object
assert obj
osprops = tool.Spatial.get_object_spatial_props(obj)
if osprops.is_editing:
if SpatialData.data["default_container"]:
@@ -103,7 +110,7 @@ class BIM_PT_spatial_decomposition(Panel):
return tool.Ifc.get()
def draw_header(self, context):
props = context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
row = self.layout.row(align=True)
row.label(text="") # empty text occupies the left of the row
icon = "HIDE_OFF" if props.is_visible else "HIDE_ON"
@@ -114,7 +121,7 @@ class BIM_PT_spatial_decomposition(Panel):
def draw(self, context):
if not SpatialDecompositionData.is_loaded:
SpatialDecompositionData.load()
self.props = context.scene.BIMSpatialDecompositionProperties
self.props = tool.Spatial.get_spatial_props()
if SpatialDecompositionData.data["default_container"]:
row = self.layout.row(align=True)
@@ -233,7 +240,7 @@ class BIM_PT_grids(Panel):
self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids")
def draw_header(self, context):
props = context.scene.BIMGridProperties
props = tool.Spatial.get_grid_props()
row = self.layout.row(align=True)
row.label(text="") # empty text occupies the left of the row
icon = "HIDE_OFF" if props.is_visible else "HIDE_ON"
@@ -243,25 +250,36 @@ class BIM_PT_grids(Panel):
class BIM_UL_containers_manager(UIList):
icon_by_class = {
"IfcProject": "FILE",
"IfcSite": "WORLD",
"IfcBuilding": "HOME",
"IfcBuildingStorey": "LINENUMBERS_OFF",
"IfcSpace": "ANTIALIASED",
"IfcFacilityPart": "MOD_FLUID",
"IfcBridgePart": "MOD_FLUID",
"IfcFacilityPartCommon": "MOD_FLUID",
"IfcMarinePart": "MOD_FLUID",
"IfcRailwayPart": "MOD_FLUID",
"IfcRoadPart": "MOD_FLUID",
}
def __init__(self):
self.use_filter_show = True
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMSpatialDecompositionProperties,
item: BIMContainer,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
icon = {
"IfcProject": "FILE",
"IfcSite": "WORLD",
"IfcBuilding": "HOME",
"IfcBuildingStorey": "LINENUMBERS_OFF",
"IfcSpace": "ANTIALIASED",
"IfcFacilityPart": "MOD_FLUID",
"IfcBridgePart": "MOD_FLUID",
"IfcFacilityPartCommon": "MOD_FLUID",
"IfcMarinePart": "MOD_FLUID",
"IfcRailwayPart": "MOD_FLUID",
"IfcRoadPart": "MOD_FLUID",
}.get(item.ifc_class, "META_PLANE")
icon = self.icon_by_class.get(item.ifc_class, "META_PLANE")
split = row.split(factor=0.85)
if item.long_name:
split2 = split.split(factor=0.7)
@@ -275,7 +293,7 @@ class BIM_UL_containers_manager(UIList):
row.prop(item, "name", emboss=False, text="", icon=icon)
split.prop(item, "elevation", emboss=False, text="")
def draw_hierarchy(self, row, item):
def draw_hierarchy(self, row: bpy.types.UILayout, item: BIMContainer) -> None:
if item.level_index:
for i in range(0, item.level_index - 1):
row.label(text="", icon="BLANK1")
@@ -293,11 +311,12 @@ class BIM_UL_containers_manager(UIList):
def draw_filter(self, context, layout):
row = layout.row()
row.prop(context.scene.BIMSpatialDecompositionProperties, "container_filter", text="", icon="VIEWZOOM")
props = tool.Spatial.get_spatial_props()
row.prop(props, "container_filter", text="", icon="VIEWZOOM")
def filter_items(self, context, data, propname):
def filter_items(self, context: bpy.types.Context, data: BIMSpatialDecompositionProperties, propname: str):
items = getattr(data, propname)
filter_name = context.scene.BIMSpatialDecompositionProperties.container_filter.lower()
filter_name = data.container_filter.lower()
filter_flags = [self.bitflag_filter_item] * len(items)
for idx, item in enumerate(items):
@@ -313,7 +332,7 @@ class BIM_UL_containers_manager(UIList):
return filter_flags, []
items = getattr(data, propname)
filter_name = context.scene.BIMSpatialDecompositionProperties.container_filter
filter_name = data.container_filter
filtered = bpy.types.UI_UL_list.filter_items_by_name(filter_name, self.bitflag_filter_item, items, "name")
return filtered, []
@@ -326,7 +345,18 @@ class BIM_UL_elements(UIList):
icon_id = "DISCLOSURE_TRI_DOWN" if is_expanded else "DISCLOSURE_TRI_RIGHT"
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).element_index = index
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, fit_flag):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMSpatialDecompositionProperties,
item: Element,
icon,
active_data,
active_propname,
index,
fit_flag,
):
if item:
row = layout.row(align=True)
for _ in range(item.level):
@@ -341,11 +371,12 @@ class BIM_UL_elements(UIList):
def draw_filter(self, context, layout):
row = layout.row()
row.prop(context.scene.BIMSpatialDecompositionProperties, "element_filter", text="", icon="VIEWZOOM")
props = tool.Spatial.get_spatial_props()
row.prop(props, "element_filter", text="", icon="VIEWZOOM")
def filter_items(self, context, data, propname):
def filter_items(self, context: bpy.types.Context, data: BIMSpatialDecompositionProperties, propname: str):
items = getattr(data, propname)
filter_name = context.scene.BIMSpatialDecompositionProperties.element_filter
filter_name = data.element_filter
filtered = bpy.types.UI_UL_list.filter_items_by_name(filter_name, self.bitflag_filter_item, items, "name")
return filtered, []
+2 -2
View File
@@ -147,11 +147,11 @@ class Geometry(bonsai.core.tool.Geometry):
def is_locked(cls, element: ifcopenshell.entity_instance) -> bool:
if element.is_a("IfcProject"):
return True
elif tool.Root.is_spatial_element(element) and bpy.context.scene.BIMSpatialDecompositionProperties.is_locked:
elif tool.Root.is_spatial_element(element) and tool.Spatial.get_spatial_props().is_locked:
return True
elif (
element.is_a("IfcPositioningElement") or element.is_a("IfcGrid") or element.is_a("IfcGridAxis")
) and bpy.context.scene.BIMGridProperties.is_locked:
) and tool.Spatial.get_grid_props().is_locked:
return True
return False
+4 -2
View File
@@ -137,13 +137,15 @@ class Project(bonsai.core.tool.Project):
@classmethod
def set_context(cls, context):
bonsai.bim.handler.refresh_ui_data()
bpy.context.scene.BIMRootProperties.contexts = str(context.id())
rprops = tool.Root.get_root_props()
rprops.contexts = str(context.id())
@classmethod
def set_default_context(cls):
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
if context:
bpy.context.scene.BIMRootProperties.contexts = str(context.id())
rprops = tool.Root.get_root_props()
rprops.contexts = str(context.id())
@classmethod
def set_default_modeling_dimensions(cls):
+11 -3
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import ifcopenshell
import ifcopenshell.api
@@ -27,12 +28,19 @@ import bonsai.core.tool
import bonsai.core.aggregate
import bonsai.core.geometry
import bonsai.tool as tool
from typing import Union, Optional, Any, Literal
from typing import Union, Optional, Any, Literal, TYPE_CHECKING
from bonsai.bim.module.spatial.decorator import GridDecorator
from bonsai.bim.module.geometry.decorator import ItemDecorator
if TYPE_CHECKING:
from bonsai.bim.module.root.prop import BIMRootProperties
class Root(bonsai.core.tool.Root):
@classmethod
def get_root_props(cls) -> BIMRootProperties:
return bpy.context.scene.BIMRootProperties
@classmethod
def add_tracked_opening(cls, obj: bpy.types.Object, opening_type: Literal["OPENING", "BOOLEAN"]) -> None:
"""Add tracked opening or boolean object."""
@@ -112,7 +120,7 @@ class Root(bonsai.core.tool.Root):
@classmethod
def get_default_container(cls) -> Optional[ifcopenshell.entity_instance]:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if container := props.default_container:
try:
return tool.Ifc.get().by_id(container)
@@ -233,7 +241,7 @@ class Root(bonsai.core.tool.Root):
@classmethod
def reload_grid_decorator(cls) -> None:
axes = bpy.context.scene.BIMGridProperties.grid_axes
axes = tool.Spatial.get_grid_props().grid_axes
axes.clear()
for axis in tool.Ifc.get().by_type("IfcGridAxis"):
if obj := tool.Ifc.get_object(axis):
+48 -27
View File
@@ -41,12 +41,31 @@ import json
from math import pi
from mathutils import Vector, Matrix
from shapely import Polygon
from typing import Generator, Optional, Union, Literal, List, Any, Iterable
from typing import Generator, Optional, Union, Literal, List, Any, Iterable, TYPE_CHECKING
from collections import defaultdict
from natsort import natsorted
if TYPE_CHECKING:
from bonsai.bim.module.spatial.prop import (
BIMGridProperties,
BIMSpatialDecompositionProperties,
BIMObjectSpatialProperties,
)
class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_spatial_props(cls) -> BIMSpatialDecompositionProperties:
return bpy.context.scene.BIMSpatialDecompositionProperties
@classmethod
def get_object_spatial_props(cls, obj: bpy.types.Object) -> BIMObjectSpatialProperties:
return obj.BIMObjectSpatialProperties
@classmethod
def get_grid_props(cls) -> BIMGridProperties:
return bpy.context.scene.BIMGridProperties
@classmethod
def can_contain(cls, container: ifcopenshell.entity_instance, element_obj: Union[bpy.types.Object, None]) -> bool:
if not (element := tool.Ifc.get_entity(element_obj)):
@@ -81,7 +100,8 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def disable_editing(cls, obj: bpy.types.Object) -> None:
obj.BIMObjectSpatialProperties.is_editing = False
props = cls.get_object_spatial_props(obj)
props.is_editing = False
@classmethod
def duplicate_object_and_data(cls, obj: bpy.types.Object) -> bpy.types.Object:
@@ -92,8 +112,9 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def enable_editing(cls, obj: bpy.types.Object) -> None:
obj.BIMObjectSpatialProperties.is_editing = True
obj.BIMObjectSpatialProperties.relating_container_object = None
props = cls.get_object_spatial_props(obj)
props.is_editing = True
props.relating_container_object = None
@classmethod
def get_container(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
@@ -207,7 +228,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_container_elements_grouped_by_classification(cls, container: ifcopenshell.entity_instance) -> dict:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
results = {}
if props.should_include_children:
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
@@ -254,7 +275,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_container_elements_grouped_by_type(cls, container: ifcopenshell.entity_instance) -> dict:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
results: defaultdict[str, dict[int, Any]] = defaultdict(dict)
if props.should_include_children:
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
@@ -280,7 +301,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def load_contained_elements(cls) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
props.elements.clear()
if not (container := props.active_container):
return
@@ -295,7 +316,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def load_contained_elements_by_type(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
results = cls.get_container_elements_grouped_by_type(container)
expanded_elements = json.loads(props.expanded_elements)
@@ -350,7 +371,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def load_contained_elements_by_decomposition(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
expanded_elements = json.loads(props.expanded_elements)
expanded_ifc_ids = expanded_elements.get("IFC_ID", [])
@@ -381,7 +402,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def load_contained_elements_by_classification(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
expanded_elements = json.loads(props.expanded_elements)
expanded_classifications = expanded_elements.get("CLASSIFICATION", [])
expanded_classifications_r = expanded_elements.get("CLASSIFICATION_R", [])
@@ -445,7 +466,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def import_spatial_decomposition(cls) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
previous_container_index = props.active_container_index
props.containers.clear()
cls.contracted_containers = json.loads(props.contracted_containers)
@@ -456,7 +477,7 @@ class Spatial(bonsai.core.tool.Spatial):
def import_spatial_element(cls, element: ifcopenshell.entity_instance, level_index: int) -> None:
if not element.is_a("IfcProject") and not tool.Root.is_spatial_element(element):
return
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
new = props.containers.add()
new.ifc_class = element.is_a()
new["name"] = element.Name or "Unnamed"
@@ -485,28 +506,28 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_active_container(cls) -> Union[ifcopenshell.entity_instance, None]:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
if props.active_container_index < len(props.containers):
container = tool.Ifc.get().by_id(props.containers[props.active_container_index].ifc_definition_id)
return container
@classmethod
def contract_container(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
contracted_containers = json.loads(props.contracted_containers)
contracted_containers.append(container.id())
props.contracted_containers = json.dumps(contracted_containers)
@classmethod
def expand_container(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
contracted_containers = json.loads(props.contracted_containers)
contracted_containers.remove(container.id())
props.contracted_containers = json.dumps(contracted_containers)
@classmethod
def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
if props.element_mode == "TYPE":
cls.toggle_container_element_by_type(element_index, is_recursive)
elif props.element_mode == "DECOMPOSITION":
@@ -516,7 +537,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def toggle_container_element_by_type(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
element = props.elements[element_index]
@@ -565,7 +586,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def toggle_container_element_by_decomposition(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
element = props.elements[element_index]
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
expanded_elements_list: list[Union[str, int]] = expanded_elements.setdefault("IFC_ID", [])
@@ -598,7 +619,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def toggle_container_element_by_classification(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
expanded_elements: dict[str, list[str]] = json.loads(props.expanded_elements)
expanded_elements_list: list[str] = expanded_elements.setdefault("CLASSIFICATION", [])
@@ -679,7 +700,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_boundary_lines_from_context_visible_objects(cls) -> list[shapely.LineString]:
props = props = tool.Model.get_model_props()
props = tool.Model.get_model_props()
calculation_rl = props.rl3
container = tool.Root.get_default_container()
container_obj = tool.Ifc.get_object(container)
@@ -1154,8 +1175,8 @@ class Spatial(bonsai.core.tool.Spatial):
def set_default_container(cls, container: ifcopenshell.entity_instance) -> None:
from bonsai.bim.module.spatial.data import SpatialDecompositionData
assert bpy.context
bpy.context.scene.BIMSpatialDecompositionProperties.default_container = container.id()
props = cls.get_spatial_props()
props.default_container = container.id()
SpatialDecompositionData.data["default_container"] = SpatialDecompositionData.default_container()
project = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
@@ -1212,16 +1233,16 @@ class Spatial(bonsai.core.tool.Spatial):
def set_target_container_as_default(cls) -> None:
if (
(container := tool.Root.get_default_container())
and (obj := tool.Ifc.get_object(container))
and bpy.context.active_object
and (container_obj := tool.Ifc.get_object(container))
and (obj := bpy.context.active_object)
):
props = bpy.context.active_object.BIMObjectSpatialProperties
props.container_obj = obj
props = cls.get_object_spatial_props(obj)
props.container_obj = container_obj
@classmethod
def get_filtered_elements(cls, should_filter: bool = True) -> Iterable[ifcopenshell.entity_instance]:
ifc_file = tool.Ifc.get()
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = cls.get_spatial_props()
container = ifc_file.by_id(props.active_container.ifc_definition_id)
element_filter = props.element_filter
active_element = props.active_element
+3 -2
View File
@@ -383,7 +383,8 @@ class TestUsingArrays(NewFile):
bpy.ops.mesh.primitive_cube_add()
obj = bpy.context.active_object
bpy.context.scene.BIMRootProperties.ifc_product = "IfcElement"
rprops = tool.Root.get_root_props()
rprops.ifc_product = "IfcElement"
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
bpy.ops.bim.add_array()
@@ -520,7 +521,7 @@ class TestApplyIfcMaterialChanges(NewFile):
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
with_opening = bpy.context.active_object
with_opening.name = "With Opening"
props = bpy.context.scene.BIMRootProperties
props = tool.Root.get_root_props()
props.representation_obj = with_opening
bpy.ops.bim.add_element(ifc_product="IfcFeatureElement", ifc_class="IfcOpeningElement")
+4 -2
View File
@@ -97,7 +97,8 @@ class TestSetContext(NewFile):
tool.Ifc.set(ifc)
context = ifc.createIfcGeometricRepresentationContext()
subject.set_context(context)
assert bpy.context.scene.BIMRootProperties.contexts == str(context.id())
rprops = tool.Root.get_root_props()
assert rprops.contexts == str(context.id())
class TestSetDefaultContext(NewFile):
@@ -115,7 +116,8 @@ class TestSetDefaultContext(NewFile):
target_view="MODEL_VIEW",
)
subject.set_default_context()
assert bpy.context.scene.BIMRootProperties.contexts == str(body.id())
rprops = tool.Root.get_root_props()
assert rprops.contexts == str(body.id())
class TestSetDefaultModelingDimensions(NewFile):
+4 -2
View File
@@ -191,8 +191,10 @@ class TestReassignClass(NewFile):
slabs = [tool.Ifc.get_object(e) for e in ifc_file.by_type("IfcSlab")]
assert len(slabs) == 3
tool.Blender.set_objects_selection(context, slabs[0], (slabs[1],))
context.scene.BIMRootProperties.ifc_product = "IfcElement"
context.scene.BIMRootProperties.ifc_class = "IfcWall"
props = tool.Root.get_root_props()
props.ifc_product = "IfcElement"
props.ifc_class = "IfcWall"
bpy.ops.bim.reassign_class()
assert len(ifc_file.by_type("IfcWall")) == 3
+4 -2
View File
@@ -129,7 +129,8 @@ class TestDisableEditing(NewFile):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing(obj)
subject.disable_editing(obj)
assert obj.BIMObjectSpatialProperties.is_editing is False
props = tool.Spatial.get_object_spatial_props(obj)
assert props.is_editing is False
class TestDuplicateObjectAndData(NewFile):
@@ -148,7 +149,8 @@ class TestEnableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing(obj)
assert obj.BIMObjectSpatialProperties.is_editing is True
props = tool.Spatial.get_object_spatial_props(obj)
assert props.is_editing is True
class TestGetContainer(NewFile):