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):
@@ -28,11 +28,8 @@ def remove_cost_item_quantity(
removed.
:param cost_item: The IfcCostItem that the quantity is assigned to
:type cost_item: ifcopenshell.entity_instance
:param physical_quantity: The IfcPhysicalQuantity to remove
:type physical_quantity: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -46,11 +43,9 @@ def remove_cost_item_quantity(
ifcopenshell.api.cost.remove_cost_item(model,
cost_item=item, physical_quantity=quantity)
"""
settings = {"cost_item": cost_item, "physical_quantity": physical_quantity}
if len(file.get_inverse(settings["physical_quantity"])) == 1:
file.remove(settings["physical_quantity"])
if len(file.get_inverse(physical_quantity)) == 1:
file.remove(physical_quantity)
return
quantities = list(settings["cost_item"].CostQuantities or [])
quantities.remove(settings["physical_quantity"])
settings["cost_item"].CostQuantities = quantities
quantities = list(cost_item.CostQuantities or [])
quantities.remove(physical_quantity)
cost_item.CostQuantities = quantities
@@ -28,11 +28,8 @@ def remove_cost_value(
:param parent: The IfcCostItem, IfcConstructionResource, or IfcCostValue
that the IfcCostValue is assigned to.
:type parent: ifcopenshell.entity_instance
:param cost_value: The IfcCostValue that you want to remove
:type parent: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -48,20 +45,18 @@ def remove_cost_value(
ifcopenshell.api.cost.remove_cost_value(model, parent=item, cost_value=value)
"""
settings = {"parent": parent, "cost_value": cost_value}
if len(file.get_inverse(settings["cost_value"])) == 1:
file.remove(settings["cost_value"])
if len(file.get_inverse(cost_value)) == 1:
file.remove(cost_value)
# TODO deep purge
elif settings["parent"].is_a("IfcCostItem"):
values = list(settings["parent"].CostValues)
values.remove(settings["cost_value"])
settings["parent"].CostValues = values if values else None
elif settings["parent"].is_a("IfcConstructionResource"):
values = list(settings["parent"].BaseCosts)
values.remove(settings["cost_value"])
settings["parent"].BaseCosts = values if values else None
elif settings["parent"].is_a("IfcCostValue"):
components = list(settings["parent"].Components)
components.remove(settings["cost_value"])
settings["parent"].Components = components if components else None
elif parent.is_a("IfcCostItem"):
values = list(parent.CostValues)
values.remove(cost_value)
parent.CostValues = values if values else None
elif parent.is_a("IfcConstructionResource"):
values = list(parent.BaseCosts)
values.remove(cost_value)
parent.BaseCosts = values if values else None
elif parent.is_a("IfcCostValue"):
components = list(parent.Components)
components.remove(cost_value)
parent.Components = components if components else None
@@ -23,9 +23,7 @@ def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance
"""Removes a grid axis from a grid
:param axis: The IfcGridAxis you want to remove.
:type axis: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -92,19 +92,17 @@ def assign_profile(
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"material_profile": material_profile, "profile": profile}
return usecase.execute()
return usecase.execute(material_profile, profile)
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self) -> None:
def execute(self, material_profile: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance) -> None:
# TODO: handle composite profiles
old_profile = self.settings["material_profile"].Profile
self.settings["material_profile"].Profile = self.settings["profile"]
for profile_set in self.settings["material_profile"].ToMaterialProfileSet:
old_profile = material_profile.Profile
material_profile.Profile = profile
for profile_set in material_profile.ToMaterialProfileSet:
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
@@ -113,20 +111,20 @@ class Usecase:
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_profile(element)
self.change_profile(element, profile)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_profile(element)
self.change_profile(element, profile)
if old_profile and len(self.file.get_inverse(old_profile)) == 0:
# TODO: check remove deep
self.file.remove(old_profile)
def change_profile(self, element: ifcopenshell.entity_instance) -> None:
def change_profile(self, element: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance) -> None:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = self.settings["profile"]
subelement.SweptArea = profile
@@ -27,9 +27,7 @@ def remove_organisation(file: ifcopenshell.file, organisation: ifcopenshell.enti
removed.
:param organisation: The IfcOrganization to remove
:type organisation: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -39,31 +37,29 @@ def remove_organisation(file: ifcopenshell.file, organisation: ifcopenshell.enti
identification="AWB", name="Architects Without Ballpens")
ifcopenshell.api.owner.remove_organisation(model, organisation=organisation)
"""
settings = {"organisation": organisation}
for role in settings["organisation"].Roles or []:
for role in organisation.Roles or []:
if len(file.get_inverse(role)) == 1:
ifcopenshell.api.owner.remove_role(file, role=role)
for address in settings["organisation"].Addresses or []:
for address in organisation.Addresses or []:
if len(file.get_inverse(address)) == 1:
ifcopenshell.api.owner.remove_address(file, address=address)
for inverse in file.get_inverse(settings["organisation"]):
for inverse in file.get_inverse(organisation):
if inverse.is_a("IfcOrganizationRelationship"):
if inverse.RelatingOrganization == settings["organisation"]:
if inverse.RelatingOrganization == organisation:
file.remove(inverse)
elif inverse.RelatedOrganizations == (settings["organisation"],):
elif inverse.RelatedOrganizations == (organisation,):
file.remove(inverse)
elif inverse.is_a("IfcDocumentInformation"):
if inverse.Editors == (settings["organisation"],):
if inverse.Editors == (organisation,):
inverse.Editors = None
elif inverse.is_a("IfcPersonAndOrganization"):
ifcopenshell.api.owner.remove_person_and_organisation(file, person_and_organisation=inverse)
elif inverse.is_a("IfcActor"):
ifcopenshell.api.root.remove_product(file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship") and not inverse.is_a("IfcOrganizationRelationship"):
if inverse.RelatedResourceObjects == (settings["organisation"],):
if inverse.RelatedResourceObjects == (organisation,):
file.remove(inverse)
elif inverse.is_a("IfcApplication"):
ifcopenshell.api.owner.remove_application(file, application=inverse)
file.remove(settings["organisation"])
file.remove(organisation)
@@ -29,9 +29,7 @@ def remove_person(file: ifcopenshell.file, person: ifcopenshell.entity_instance)
the only responsile person for them.
:param person: The IfcPerson to remove
:type person: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -41,31 +39,30 @@ def remove_person(file: ifcopenshell.file, person: ifcopenshell.entity_instance)
identification="bobthebuilder", family_name="Thebuilder", given_name="Bob")
ifcopenshell.api.owner.remove_person(model, person=person)
"""
settings = {"person": person}
for role in settings["person"].Roles or []:
for role in person.Roles or []:
if len(file.get_inverse(role)) == 1:
ifcopenshell.api.owner.remove_role(file, role=role)
for address in settings["person"].Addresses or []:
for address in person.Addresses or []:
if len(file.get_inverse(address)) == 1:
ifcopenshell.api.owner.remove_address(file, address=address)
for inverse in file.get_inverse(settings["person"]):
for inverse in file.get_inverse(person):
if inverse.is_a("IfcWorkControl"):
if inverse.Creators == (settings["person"],):
if inverse.Creators == (person,):
inverse.Creators = None
elif inverse.is_a("IfcInventory"):
if inverse.ResponsiblePersons == (settings["person"],):
if inverse.ResponsiblePersons == (person,):
# in IFC2X3 ResponsiblePersons is not optional and without it IfcInventory is not valid
if file.schema == "IFC2X3":
ifcopenshell.api.root.remove_product(file, product=inverse)
elif inverse.is_a("IfcDocumentInformation"):
if inverse.Editors == (settings["person"],):
if inverse.Editors == (person,):
inverse.Editors = None
elif inverse.is_a("IfcPersonAndOrganization"):
ifcopenshell.api.owner.remove_person_and_organisation(file, person_and_organisation=inverse)
elif inverse.is_a("IfcActor"):
ifcopenshell.api.root.remove_product(file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship"):
if inverse.RelatedResourceObjects == (settings["person"],):
if inverse.RelatedResourceObjects == (person,):
file.remove(inverse)
file.remove(settings["person"])
file.remove(person)
@@ -80,8 +80,8 @@ def assign_lag_time(
# for whatever reason.
ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1D")
"""
lag_value = file.create_entity("IfcDuration", ifcopenshell.util.date.datetime2ifc(lag_value, "IfcDuration"))
lag_time = file.create_entity("IfcLagTime", DurationType=duration_type, LagValue=lag_value)
duration = file.create_entity("IfcDuration", ifcopenshell.util.date.datetime2ifc(lag_value, "IfcDuration"))
lag_time = file.create_entity("IfcLagTime", DurationType=duration_type, LagValue=duration)
if rel_sequence.is_a("IfcRelSequence"):
if rel_sequence.TimeLag and len(file.get_inverse(rel_sequence.TimeLag)) == 1:
file.remove(rel_sequence.TimeLag)
@@ -25,9 +25,7 @@ def unassign_lag_time(file: ifcopenshell.file, rel_sequence: ifcopenshell.entity
The schedule is cascaded afterwards.
:param rel_sequence: The sequence to remove the lag time from.
:type rel_sequence: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -57,12 +55,8 @@ def unassign_lag_time(file: ifcopenshell.file, rel_sequence: ifcopenshell.entity
# What if you didn't?
ifcopenshell.api.sequence.unassign_lag_time(model, rel_sequence=sequence)
"""
settings = {
"rel_sequence": rel_sequence,
}
if len(file.get_inverse(settings["rel_sequence"].TimeLag)) == 1:
file.remove(settings["rel_sequence"].TimeLag)
if len(file.get_inverse(rel_sequence.TimeLag)) == 1:
file.remove(rel_sequence.TimeLag)
else:
settings["rel_sequence"].TimeLag = None
ifcopenshell.api.sequence.cascade_schedule(file, task=settings["rel_sequence"].RelatedProcess)
rel_sequence.TimeLag = None
ifcopenshell.api.sequence.cascade_schedule(file, task=rel_sequence.RelatedProcess)
@@ -29,18 +29,14 @@ def remove_structural_analysis_model(
:param structural_analysis_model: The IfcStructuralAnalysisModel to
remove.
:type structural_analysis_model: ifcopenshell.entity_instance
:return: None
:rtype: None
"""
settings = {"structural_analysis_model": structural_analysis_model}
for rel in settings["structural_analysis_model"].IsGroupedBy or []:
for rel in structural_analysis_model.IsGroupedBy or []:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["structural_analysis_model"].OwnerHistory
file.remove(settings["structural_analysis_model"])
history = structural_analysis_model.OwnerHistory
file.remove(structural_analysis_model)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -28,23 +28,21 @@ def remove_structural_boundary_condition(
:param connection: The IfcStructuralConnection to remove the condition
from. If omitted, it is assumed to be an orphaned condition.
:type connection: ifcopenshell.entity_instance,optional
:param boundary_condition: The IfcBoundaryCondition to remove.
:type boundary_condition: ifcopenshell.entity_instance, optional.
:return: None
:rtype: None
"""
settings = {"connection": connection, "boundary_condition": boundary_condition}
if settings["connection"]:
if connection:
# remove boundary condition from a connection
if not settings["connection"].AppliedCondition:
if not connection.AppliedCondition:
return
if len(file.get_inverse(settings["connection"].AppliedCondition)) == 1:
file.remove(settings["connection"].AppliedCondition)
settings["connection"].AppliedCondition = None
applied_condition = connection.AppliedCondition
if file.get_total_inverses(applied_condition) == 1:
file.remove(applied_condition)
connection.AppliedCondition = None
else:
assert boundary_condition, "Either connection or boundary_condition must be provided."
# remove the boundary condition
for conn in file.get_inverse(settings["boundary_condition"]):
for conn in file.get_inverse(boundary_condition):
conn.AppliedCondition = None
file.remove(settings["boundary_condition"])
file.remove(boundary_condition)
@@ -22,10 +22,6 @@ def remove_structural_load(file: ifcopenshell.file, structural_load: ifcopenshel
"""Removes a structural load
:param structural_load: The IfcStructuralLoad to remove.
:type structural_load: ifcopenshell.entity_instance
:return: None
:rtype: None
"""
settings = {"structural_load": structural_load}
file.remove(settings["structural_load"])
file.remove(structural_load)
@@ -25,18 +25,14 @@ def remove_structural_load_case(file: ifcopenshell.file, load_case: ifcopenshell
"""Removes a structural load case
:param load_case: The IfcStructuralLoadCase to remove.
:type load_case: ifcopenshell.entity_instance
:return: None
:rtype: None
"""
settings = {"load_case": load_case}
# TODO: do a deep purge
for rel in settings["load_case"].IsGroupedBy or []:
for rel in load_case.IsGroupedBy or []:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["load_case"].OwnerHistory
file.remove(settings["load_case"])
history = load_case.OwnerHistory
file.remove(load_case)
ifcopenshell.util.element.remove_deep2(file, history)
@@ -25,20 +25,16 @@ def remove_structural_load_group(file: ifcopenshell.file, load_group: ifcopenshe
"""Removes a structural load group
:param load_group: The IfcStructuralLoadGroup to remove.
:type load_group: ifcopenshell.entity_instance
:return: None
:rtype: None
"""
settings = {"load_group": load_group}
# TODO: do a deep purge
for inverse in file.get_inverse(settings["load_group"]):
for inverse in file.get_inverse(load_group):
if inverse.is_a("IfcRelAssignsToGroup") and len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["load_group"].OwnerHistory
file.remove(settings["load_group"])
history = load_group.OwnerHistory
file.remove(load_group)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -835,6 +835,7 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = "
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
import ifcopenshell.api.georeference
import ifcopenshell.api.unit
prefix = get_prefix(target_units)
si_unit = get_unit_name(target_units)