mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-24 13:56:50 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60ae4e80ce | |||
| 05ec36a37d | |||
| dc8ea31138 | |||
| faaea7db49 | |||
| b14ff94303 | |||
| b3dcfbf0dc | |||
| b68109d32b | |||
| 2ea3fe4ab9 | |||
| 94c159ccd0 | |||
| e4eead6662 | |||
| ee4e92dfa3 | |||
| f4ce2965f9 | |||
| 5fb7e6e375 | |||
| c399eb259a | |||
| 5ccdf187d5 | |||
| d53351d218 | |||
| 08a82c7b57 | |||
| 499cdbb9b8 | |||
| 4a5d1eba0b | |||
| 70091aa28b | |||
| 3d566cc401 | |||
| 6eb3c48144 | |||
| 44d180007e | |||
| 7e8060b224 | |||
| d408568d01 | |||
| 47704b1625 | |||
| 23fee9c8c8 | |||
| c5d1801e1e |
@@ -142,6 +142,7 @@ classes = [
|
||||
ui.BIM_PT_tabs,
|
||||
# Project overview
|
||||
ui.BIM_PT_tab_project_info,
|
||||
ui.BIM_PT_tab_project_tree,
|
||||
ui.BIM_PT_tab_project_setup,
|
||||
ui.BIM_PT_tab_geometry,
|
||||
ui.BIM_PT_tab_stakeholders,
|
||||
|
||||
@@ -1702,6 +1702,7 @@ class IfcImporter:
|
||||
if aggregate["element"].is_a("IfcElementType"):
|
||||
self.type_collection.children.link(aggregate["collection"])
|
||||
continue
|
||||
self.project["blender"].children.link(aggregate["collection"])
|
||||
|
||||
def create_materials(self) -> None:
|
||||
for material in self.file.by_type("IfcMaterial"):
|
||||
|
||||
@@ -55,21 +55,24 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
|
||||
if not relating_obj:
|
||||
return
|
||||
|
||||
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
if obj == relating_obj:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
result = core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
if not result:
|
||||
self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}")
|
||||
try:
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
except core.IncompatibleAggregateError:
|
||||
self.report({"ERROR"}, f"Cannot aggregate {obj.name} to {relating_obj.name}")
|
||||
except core.AggregateRepresentationError:
|
||||
self.report({"ERROR"}, f"Cannot aggregate to {relating_obj.name} with a body representation")
|
||||
|
||||
|
||||
class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -29,13 +29,9 @@ def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CoveringTool, after={"bim.structural_tool"}, separator=False, group=False)
|
||||
bpy.types.Scene.BIMCoveringProperties = bpy.props.PointerProperty(type=prop.BIMCoveringProperties)
|
||||
# bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
|
||||
# bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.CoveringTool)
|
||||
del bpy.types.Scene.BIMCoveringProperties
|
||||
# del bpy.types.Object.BIMObjectSpatialProperties
|
||||
# del bpy.types.Scene.BIMSpatialManagerProperties
|
||||
|
||||
@@ -29,6 +29,7 @@ import subprocess
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
@@ -1537,14 +1538,7 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
try:
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
except core.CameraNotAvailableError:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"The drawing view is not available. Ensure you have not excluded it in the active view layer.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
@@ -1638,7 +1632,7 @@ class ReloadDrawingStyles(bpy.types.Operator):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
camera_props = context.active_object.data.BIMCameraProperties
|
||||
camera_props = context.scene.camera.data.BIMCameraProperties
|
||||
|
||||
# added this part as a temporary fallback
|
||||
# TODO: should remove it a bit later when projects get more accommodated
|
||||
|
||||
@@ -596,6 +596,7 @@ class EditMaterialSetItemProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
attributes = blenderbim.bim.helper.export_attributes(self.props.material_set_item_profile_attributes)
|
||||
profile = tool.Ifc.get().by_id(self.material_set_item).Profile
|
||||
ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes)
|
||||
self.props.active_material_set_item_id = 0
|
||||
self.props.material_set_item_profile_attributes.clear()
|
||||
model_profile.DumbProfileRegenerator().regenerate_from_profile_def(profile)
|
||||
|
||||
@@ -702,7 +703,7 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ExpandMaterialCategory(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_material_category"
|
||||
bl_label = "Expand Material Category"
|
||||
bl_description = "Expand material category.\n\nSHIFT+CLICK to expand all material categories"
|
||||
bl_description = "SHIFT+CLICK to expand all material categories"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
category: bpy.props.StringProperty()
|
||||
expand_all: bpy.props.BoolProperty(name="Expand All", default=False, options={"SKIP_SAVE"})
|
||||
@@ -731,7 +732,7 @@ class ExpandMaterialCategory(bpy.types.Operator):
|
||||
class ContractMaterialCategory(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_material_category"
|
||||
bl_label = "Contract Material Category"
|
||||
bl_description = "Contract material category.\n\nSHIFT+CLICK to contract all material categories"
|
||||
bl_description = "SHIFT+CLICK to contract all material categories"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
category: bpy.props.StringProperty()
|
||||
contract_all: bpy.props.BoolProperty(name="Contract All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@@ -121,12 +121,16 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
for arg_name in inputs:
|
||||
arg_info = inputs[arg_name]
|
||||
new_attr = patch_args.add()
|
||||
data_type = arg_info.get("type", "str")
|
||||
if isinstance(data_type, list):
|
||||
data_type = [dt for dt in data_type if dt != "NoneType"][0]
|
||||
new_attr.data_type = {
|
||||
"Literal": "string",
|
||||
"str": "string",
|
||||
"float": "float",
|
||||
"int": "integer",
|
||||
"bool": "boolean",
|
||||
}[arg_info.get("type", "str")]
|
||||
}[data_type]
|
||||
new_attr.name = arg_name
|
||||
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -23,6 +23,7 @@ classes = (
|
||||
operator.CalculateCircleRadius,
|
||||
operator.CalculateEdgeLengths,
|
||||
operator.CalculateFaceAreas,
|
||||
operator.CalculateFormworkArea,
|
||||
operator.CalculateObjectVolumes,
|
||||
operator.CalculateSingleQuantity,
|
||||
operator.PerformQuantityTakeOff,
|
||||
|
||||
@@ -84,6 +84,21 @@ class CalculateObjectVolumes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateFormworkArea(bpy.types.Operator):
|
||||
bl_idname = "bim.calculate_formwork_area"
|
||||
bl_label = "Calculate Formwork Area"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and context.active_object
|
||||
|
||||
def execute(self, context):
|
||||
result = helper.calculate_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
|
||||
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_single_quantity"
|
||||
bl_label = "Calculate Single Quantity"
|
||||
|
||||
@@ -87,14 +87,16 @@ class BIM_PT_qto_simple(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "qto_result", text="Results")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.operator("bim.calculate_circle_radius")
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.operator("bim.calculate_edge_lengths")
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.operator("bim.calculate_face_areas")
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.operator("bim.calculate_object_volumes")
|
||||
row = layout.row()
|
||||
row.operator("bim.calculate_formwork_area")
|
||||
|
||||
|
||||
class BIM_PT_qto_cost(bpy.types.Panel):
|
||||
|
||||
@@ -41,20 +41,7 @@ quantitytypes_enum = {}
|
||||
|
||||
|
||||
def setup_quantity_types_enum():
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConstructionResource.htm#Table-7.3.3.7.1.3.H
|
||||
resources = {
|
||||
"IfcCrewResource": ("IfcQuantityTime",),
|
||||
"IfcLaborResource": ("IfcQuantityTime",),
|
||||
"IfcSubContractResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionEquipmentResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionMaterialResource": (
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityWeight",
|
||||
),
|
||||
"IfcConstructionProductResource": ("IfcQuantityCount",),
|
||||
}
|
||||
resources = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES
|
||||
for resource, quantities in resources.items():
|
||||
quantitytypes_enum[resource] = [(q, q, "") for q in quantities]
|
||||
|
||||
|
||||
@@ -22,31 +22,32 @@ from . import ui, prop, operator, workspace
|
||||
classes = (
|
||||
operator.AssignContainer,
|
||||
operator.ChangeSpatialLevel,
|
||||
operator.ContractContainer,
|
||||
operator.CopyToContainer,
|
||||
operator.DeleteContainer,
|
||||
operator.DereferenceStructure,
|
||||
operator.DisableEditingContainer,
|
||||
operator.EditContainerAttributes,
|
||||
operator.EnableEditingContainer,
|
||||
operator.ExpandContainer,
|
||||
operator.LoadContainerManager,
|
||||
operator.ReferenceStructure,
|
||||
operator.RemoveContainer,
|
||||
operator.SelectContainer,
|
||||
operator.SelectSimilarContainer,
|
||||
operator.SelectProduct,
|
||||
operator.LoadContainerManager,
|
||||
operator.EditContainerAttributes,
|
||||
operator.AddBuildingStorey,
|
||||
operator.ContractContainer,
|
||||
operator.ExpandContainer,
|
||||
operator.DeleteContainer,
|
||||
operator.SelectDecomposedElements,
|
||||
operator.SelectProduct,
|
||||
operator.SelectSimilarContainer,
|
||||
prop.SpatialElement,
|
||||
prop.Element,
|
||||
prop.BIMSpatialProperties,
|
||||
prop.BIMObjectSpatialProperties,
|
||||
prop.BIMContainer,
|
||||
prop.BIMSpatialManagerProperties,
|
||||
prop.BIMProjectTreeProperties,
|
||||
ui.BIM_PT_spatial,
|
||||
ui.BIM_UL_containers,
|
||||
ui.BIM_UL_containers_manager,
|
||||
ui.BIM_PT_SpatialManager,
|
||||
ui.BIM_UL_elements,
|
||||
ui.BIM_PT_project_tree,
|
||||
workspace.Hotkey,
|
||||
)
|
||||
|
||||
@@ -56,7 +57,7 @@ def register():
|
||||
bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
|
||||
bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties)
|
||||
bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
|
||||
bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties)
|
||||
bpy.types.Scene.BIMProjectTreeProperties = bpy.props.PointerProperty(type=prop.BIMProjectTreeProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
@@ -64,4 +65,4 @@ def unregister():
|
||||
bpy.utils.unregister_tool(workspace.SpatialTool)
|
||||
del bpy.types.Scene.BIMSpatialProperties
|
||||
del bpy.types.Object.BIMObjectSpatialProperties
|
||||
del bpy.types.Scene.BIMSpatialManagerProperties
|
||||
del bpy.types.Scene.BIMProjectTreeProperties
|
||||
|
||||
@@ -23,6 +23,7 @@ import ifcopenshell.util.element
|
||||
|
||||
def refresh():
|
||||
SpatialData.is_loaded = False
|
||||
ProjectTreeData.is_loaded = False
|
||||
|
||||
|
||||
class SpatialData:
|
||||
@@ -31,28 +32,28 @@ class SpatialData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"parent_container_id": cls.parent_container_id(),
|
||||
"is_directly_contained": cls.is_directly_contained(),
|
||||
"label": cls.label(),
|
||||
"references": cls.references(),
|
||||
"containers": cls.containers(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
cls.data["poll"] = cls.poll()
|
||||
if cls.data["poll"]:
|
||||
cls.data.update(
|
||||
{
|
||||
"parent_container_id": cls.parent_container_id(),
|
||||
"is_directly_contained": cls.is_directly_contained(),
|
||||
"label": cls.label(),
|
||||
"references": cls.references(),
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def containers(cls):
|
||||
results = {}
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for container in spatial_elements:
|
||||
results[container.id()] = {
|
||||
"type": container.is_a(),
|
||||
"id": container.id(),
|
||||
}
|
||||
return results
|
||||
def poll(cls):
|
||||
if not bpy.context.active_object:
|
||||
return False
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return False
|
||||
if element.is_a("IfcElement") or element.is_a("IfcAnnotation") or element.is_a("IfcGrid"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def parent_container_id(cls):
|
||||
@@ -81,3 +82,29 @@ class SpatialData:
|
||||
@classmethod
|
||||
def is_directly_contained(cls):
|
||||
return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure", False))
|
||||
|
||||
|
||||
class ProjectTreeData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"containers": cls.containers(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def containers(cls):
|
||||
results = {}
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for container in spatial_elements:
|
||||
results[container.id()] = {
|
||||
"type": container.is_a(),
|
||||
"id": container.id(),
|
||||
}
|
||||
return results
|
||||
|
||||
@@ -227,26 +227,6 @@ class DeleteContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
core.delete_container(tool.Ifc, tool.Spatial, tool.Geometry, container=tool.Ifc.get().by_id(self.container))
|
||||
|
||||
class AddBuildingStorey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_building_storey"
|
||||
bl_label = "Add Storey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
part_class: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
active_container = tool.Spatial.get_active_container()
|
||||
obj = tool.Ifc.get_object(active_container)
|
||||
blenderbim.core.aggregate.add_part_to_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
tool.Blender,
|
||||
obj=obj,
|
||||
part_class=self.part_class,
|
||||
part_name="Unnamed",
|
||||
)
|
||||
core.load_container_manager(tool.Spatial)
|
||||
|
||||
|
||||
class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_decomposed_elements"
|
||||
|
||||
@@ -36,27 +36,21 @@ import ifcopenshell
|
||||
|
||||
|
||||
def update_elevation(self, context):
|
||||
entity = tool.Ifc.get().by_id(self.active_container_id)
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if not obj:
|
||||
return
|
||||
obj.location.z = self.elevation
|
||||
if ifc_definition_id := self.ifc_definition_id:
|
||||
entity = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if not obj:
|
||||
return
|
||||
obj.location.z = self.elevation
|
||||
|
||||
|
||||
def update_name(self, context):
|
||||
if ifc_definition_id := self.ifc_definition_id:
|
||||
tool.Spatial.edit_container_name(tool.Ifc.get().by_id(ifc_definition_id), self.name)
|
||||
|
||||
|
||||
def update_active_container_index(self, context):
|
||||
if self.active_container_index < 0:
|
||||
return
|
||||
self.active_container_id = self.containers[self.active_container_index].ifc_definition_id
|
||||
self.container_name = self.containers[self.active_container_index].name
|
||||
self.elevation = self.containers[self.active_container_index].elevation
|
||||
|
||||
|
||||
def updateContainerName(self, context):
|
||||
props = context.scene.BIMSpatialManagerProperties
|
||||
if not props.is_container_update_enabled or self.name == "Unnamed":
|
||||
return
|
||||
tool.Spatial.edit_container_name(tool.Ifc.get().by_id(props.active_container_id), self.name)
|
||||
props.container_name = self.name
|
||||
tool.Spatial.load_contained_elements()
|
||||
|
||||
|
||||
def update_relating_container_from_object(self, context):
|
||||
@@ -104,20 +98,34 @@ class BIMObjectSpatialProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMContainer(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateContainerName)
|
||||
elevation: FloatProperty(name="Elevation", subtype="DISTANCE")
|
||||
name: StringProperty(name="Name", update=update_name)
|
||||
ifc_class: StringProperty(name="IFC Class")
|
||||
description: StringProperty(name="Description")
|
||||
long_name: StringProperty(name="Long Name")
|
||||
elevation: FloatProperty(name="Elevation", subtype="DISTANCE", update=update_elevation)
|
||||
level_index: IntProperty(name="Level Index")
|
||||
has_children: BoolProperty(name="Has Children")
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMSpatialManagerProperties(PropertyGroup):
|
||||
class Element(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_class: BoolProperty(name="Is Class", default=False)
|
||||
is_type: BoolProperty(name="Is Type", default=False)
|
||||
total: IntProperty(name="Total")
|
||||
|
||||
|
||||
class BIMProjectTreeProperties(PropertyGroup):
|
||||
containers: CollectionProperty(name="Containers", type=BIMContainer)
|
||||
contracted_containers: StringProperty(name="Contracted containers", default="[]")
|
||||
expanded_containers: StringProperty(name="Expanded containers", default="[]")
|
||||
active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
|
||||
active_container_id: IntProperty(name="Active Container Id")
|
||||
container_name: StringProperty(name="Container Name")
|
||||
elevation: FloatProperty(name="Elevation", update=update_elevation, subtype="DISTANCE")
|
||||
is_container_update_enabled: BoolProperty(name="Is Container Update Enabled", default=True) # TODO:review
|
||||
elements: CollectionProperty(name="Elements", type=Element)
|
||||
active_element_index: IntProperty(name="Active Element Index")
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
|
||||
@property
|
||||
def active_container(self):
|
||||
if self.containers and self.active_container_index < len(self.containers):
|
||||
return self.containers[self.active_container_index]
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.spatial.data import SpatialData
|
||||
from blenderbim.bim.module.spatial.data import SpatialData, ProjectTreeData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
@@ -32,14 +31,9 @@ class BIM_PT_spatial(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
if not oprops.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(oprops.ifc_definition_id):
|
||||
return False
|
||||
return True
|
||||
if not SpatialData.is_loaded:
|
||||
SpatialData.load()
|
||||
return SpatialData.data["poll"]
|
||||
|
||||
def draw(self, context):
|
||||
if not SpatialData.is_loaded:
|
||||
@@ -97,34 +91,41 @@ class BIM_UL_containers(UIList):
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_SpatialManager(Panel):
|
||||
bl_label = "Spatial Manager"
|
||||
bl_idname = "BIM_PT_SpatialManager"
|
||||
class BIM_PT_project_tree(Panel):
|
||||
bl_label = "Project Tree"
|
||||
bl_idname = "BIM_PT_project_tree"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_tab_project_setup"
|
||||
bl_parent_id = "BIM_PT_tab_project_tree"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get() and tool.Ifc.schema().name() != "IFC2X3"
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not SpatialData.is_loaded:
|
||||
SpatialData.load()
|
||||
self.props = context.scene.BIMSpatialManagerProperties
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="Load Spatial Structure")
|
||||
if 0 <= self.props.active_container_index < len(self.props.containers):
|
||||
ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="Select Children")
|
||||
spatial_data = SpatialData.data["containers"].get(ifc_definition_id, None)
|
||||
if spatial_data and spatial_data["type"] in ["IfcBuildingStorey", "IfcBuilding"]:
|
||||
row.operator("bim.add_building_storey", icon="ADD", text="Add storey").part_class = "IfcBuildingStorey"
|
||||
row.operator("bim.delete_container", icon="X", text="Delete").container = ifc_definition_id
|
||||
if not ProjectTreeData.is_loaded:
|
||||
ProjectTreeData.load()
|
||||
self.props = context.scene.BIMProjectTreeProperties
|
||||
|
||||
if self.props.active_container:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(
|
||||
text=f"Active {self.props.active_container.ifc_class}: {self.props.active_container.name}",
|
||||
icon="OUTLINER_COLLECTION",
|
||||
)
|
||||
row.operator("bim.select_decomposed_elements", icon="HIDE_OFF", text="")
|
||||
row.operator("bim.delete_container", icon="X", text="").container = (
|
||||
self.props.active_container.ifc_definition_id
|
||||
)
|
||||
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="")
|
||||
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Warning: No Active Container", icon="ERROR")
|
||||
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="")
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_containers_manager",
|
||||
"",
|
||||
@@ -132,13 +133,33 @@ class BIM_PT_SpatialManager(Panel):
|
||||
"containers",
|
||||
self.props,
|
||||
"active_container_index",
|
||||
rows=10,
|
||||
)
|
||||
|
||||
if not self.props.active_container:
|
||||
return
|
||||
|
||||
# spatial_data = ProjectTreeData.data["containers"].get(ifc_definition_id, None)
|
||||
# if spatial_data and spatial_data["type"] in ["IfcBuildingStorey", "IfcBuilding"]:
|
||||
# row.operator("bim.add_building_storey", icon="ADD", text="Add storey").part_class = "IfcBuildingStorey"
|
||||
|
||||
if not self.props.total_elements:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Contained Elements", icon="FILE_3D")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{self.props.total_elements} Contained Elements", icon="FILE_3D")
|
||||
row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_elements",
|
||||
"",
|
||||
self.props,
|
||||
"elements",
|
||||
self.props,
|
||||
"active_element_index",
|
||||
)
|
||||
row = self.layout.row()
|
||||
if 0 <= self.props.active_container_index < len(self.props.containers):
|
||||
row.prop(self.props, "container_name", text="")
|
||||
row.prop(self.props, "elevation", text="")
|
||||
op = row.operator("bim.edit_container_attributes", icon="CHECKMARK", text="Apply")
|
||||
op.container = self.props.containers[self.props.active_container_index].ifc_definition_id
|
||||
|
||||
|
||||
class BIM_UL_containers_manager(UIList):
|
||||
@@ -146,22 +167,52 @@ class BIM_UL_containers_manager(UIList):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
self.draw_hierarchy(row, item)
|
||||
split1 = row.split(factor=0.7)
|
||||
split1.prop(item, "name", emboss=False, text="")
|
||||
split2 = row.split(factor=1)
|
||||
split2.label(icon="BLANK1", text=tool.Unit.blender_format_unit(item.elevation))
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
row.prop(item, "long_name", emboss=False, text="")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.prop(item, "elevation", emboss=False, text="")
|
||||
|
||||
def draw_hierarchy(self, row, item):
|
||||
for i in range(0, item.level_index):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_container", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).container = item.ifc_definition_id
|
||||
row.operator("bim.contract_container", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN").container = (
|
||||
item.ifc_definition_id
|
||||
)
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_container", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).container = item.ifc_definition_id
|
||||
row.operator("bim.expand_container", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT").container = (
|
||||
item.ifc_definition_id
|
||||
)
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.ifc_class == "IfcSite":
|
||||
row.label(text="", icon="WORLD")
|
||||
elif item.ifc_class == "IfcBuilding":
|
||||
row.label(text="", icon="HOME")
|
||||
elif item.ifc_class == "IfcBuildingStorey":
|
||||
row.label(text="", icon="LINENUMBERS_OFF")
|
||||
elif item.ifc_class == "IfcSpace":
|
||||
row.label(text="", icon="ANTIALIASED")
|
||||
else:
|
||||
row.label(text="", icon="META_PLANE")
|
||||
|
||||
|
||||
class BIM_UL_elements(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
if item.is_class:
|
||||
row.label(text="", icon="DISCLOSURE_TRI_DOWN")
|
||||
row.label(text=item.name)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.label(text=str(item.total))
|
||||
elif item.is_type:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text="", icon="DISCLOSURE_TRI_DOWN")
|
||||
row.label(text=item.name)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.label(text=str(item.total))
|
||||
|
||||
@@ -64,16 +64,10 @@ def add_layout_hotkey(layout, text, hotkey, description):
|
||||
tool.Blender.add_layout_hotkey_operator(*args)
|
||||
|
||||
|
||||
# NOTES before adding new operators:
|
||||
# - add scene.BIMSpatialProperties
|
||||
# - add SpatialData
|
||||
|
||||
|
||||
class SpatialToolUI:
|
||||
@classmethod
|
||||
def draw(cls, context, layout):
|
||||
cls.layout = layout
|
||||
# cls.props = context.scene.BIMSpatialProperties
|
||||
cls.model_props = context.scene.BIMModelProperties
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
@@ -151,12 +145,10 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
return operator.description or ""
|
||||
|
||||
def _execute(self, context):
|
||||
# self.props = context.scene.BIMSpatialProperties
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
|
||||
# self.props = context.scene.BIMSpatialProperties
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -24,6 +24,7 @@ classes = (
|
||||
operator.AddPresentationStyle,
|
||||
operator.AddStyle,
|
||||
operator.AddSurfaceTexture,
|
||||
operator.AssignStyleToSelected,
|
||||
operator.BrowseExternalStyle,
|
||||
operator.RemoveTextureMap,
|
||||
operator.ChooseTextureMapPath,
|
||||
|
||||
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style as core
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.module.style.prop import switch_shading
|
||||
from pathlib import Path
|
||||
@@ -161,13 +162,6 @@ class UpdateCurrentStyle(bpy.types.Operator):
|
||||
update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"})
|
||||
style_id: bpy.props.IntProperty(default=0, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.selected_objects:
|
||||
cls.poll_message_set("No objects selected")
|
||||
return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
# updating all styles on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
@@ -958,3 +952,49 @@ class SaveUVToStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"INFO"}, f"UV saved to the style {style.Name}")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignStyleToSelected(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_style_to_selected"
|
||||
bl_label = "Assign Style To Selected"
|
||||
bl_description = "Assign style to the selected objects' active representations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
style_id: bpy.props.IntProperty(name="Style ID")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.selected_objects:
|
||||
cls.poll_message_set("No objects selected")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
if self.style_id == 0:
|
||||
self.report({"ERROR"}, "No style provided")
|
||||
return {"CANCELLED"}
|
||||
ifc_file = tool.Ifc.get()
|
||||
style = ifc_file.by_id(self.style_id)
|
||||
|
||||
representations: dict[ifcopenshell.entity_instance, bpy.types.Object] = {}
|
||||
for obj in context.selected_objects:
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
representation = tool.Geometry.resolve_mapped_representation(representation)
|
||||
representations.setdefault(representation, obj)
|
||||
|
||||
if not representations:
|
||||
self.report({"INFO"}, "No IFC objects with representations selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
for representation in representations:
|
||||
ifcopenshell.api.style.assign_representation_styles(
|
||||
ifc_file,
|
||||
shape_representation=representation,
|
||||
styles=[style],
|
||||
should_use_presentation_style_assignment=tool.Geometry.should_use_presentation_style_assignment(),
|
||||
)
|
||||
|
||||
tool.Geometry.reload_representation(representations.values())
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -65,6 +65,8 @@ class BIM_PT_styles(Panel):
|
||||
|
||||
row.operator("bim.duplicate_style", text="", icon="DUPLICATE").style = style.ifc_definition_id
|
||||
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
|
||||
op = row.operator("bim.assign_style_to_selected", text="", icon="BRUSH_DATA")
|
||||
op.style_id = style.ifc_definition_id
|
||||
op = row.operator("bim.unlink_style", text="", icon="UNLINKED")
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_style", text="", icon="GREASEPENCIL")
|
||||
|
||||
@@ -202,9 +202,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
default=True,
|
||||
description="If disabled, the toolbar will only load when an IFC model is active",
|
||||
)
|
||||
should_play_chaching_sound: BoolProperty(
|
||||
name="Play A Cha-Ching Sound When Project Costs Updates", default=False
|
||||
)
|
||||
should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False)
|
||||
lock_grids_on_import: BoolProperty(name="Lock Grids By Default", default=True)
|
||||
spatial_elements_unselectable: BoolProperty(name="Make Spatial Elements Unselectable By Default", default=True)
|
||||
decorations_colour: bpy.props.FloatVectorProperty(
|
||||
@@ -407,7 +405,7 @@ class BIM_PT_tabs(Panel):
|
||||
|
||||
if blenderbim.last_error:
|
||||
box = self.layout.box()
|
||||
box.alert=True
|
||||
box.alert = True
|
||||
row = box.row(align=True)
|
||||
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
|
||||
row.operator("bim.close_error", text="", icon="CANCEL")
|
||||
@@ -442,6 +440,20 @@ class BIM_PT_tab_project_info(Panel):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_project_tree(Panel):
|
||||
bl_label = "Project Tree"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "PROJECT")
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_project_setup(Panel):
|
||||
bl_label = "Project Setup"
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -490,7 +502,7 @@ class BIM_PT_tab_grouping_and_filtering(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"DEFAULT_CLOSED","HEADER_LAYOUT_EXPAND"}
|
||||
bl_options = {"DEFAULT_CLOSED", "HEADER_LAYOUT_EXPAND"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -503,8 +515,9 @@ class BIM_PT_tab_grouping_and_filtering(Panel):
|
||||
# Draws help button on the right
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="") # empty text occupies the left of the row
|
||||
row.operator("bim.open_uri", text="", icon="HELP").uri = \
|
||||
row.operator("bim.open_uri", text="", icon="HELP").uri = (
|
||||
"https://docs.ifcopenshell.org/ifcopenshell-python/selector_syntax.html"
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_tab_geometry(Panel):
|
||||
|
||||
@@ -41,9 +41,12 @@ def assign_object(
|
||||
related_obj: Optional[bpy.types.Object] = None,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if not aggregator.can_aggregate(relating_obj, related_obj):
|
||||
return
|
||||
raise IncompatibleAggregateError
|
||||
relating_object = ifc.get_entity(relating_obj)
|
||||
if aggregator.has_physical_body_representation(relating_object):
|
||||
raise AggregateRepresentationError
|
||||
rel = ifc.run(
|
||||
"aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=ifc.get_entity(relating_obj)
|
||||
"aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=relating_object
|
||||
)
|
||||
collector.assign(relating_obj)
|
||||
collector.assign(related_obj)
|
||||
@@ -84,3 +87,11 @@ def add_part_to_object(
|
||||
part_obj = blender.create_ifc_object(ifc_class=part_class, name=part_name)
|
||||
assign_object(ifc, aggregator, collector, relating_obj=obj, related_obj=part_obj)
|
||||
blender.set_active_object(obj)
|
||||
|
||||
|
||||
class IncompatibleAggregateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AggregateRepresentationError(Exception):
|
||||
pass
|
||||
|
||||
@@ -484,12 +484,4 @@ def activate_drawing_view(
|
||||
drawing_tool.import_annotations_in_group(drawing_tool.get_drawing_group(drawing))
|
||||
blender.activate_camera(camera)
|
||||
drawing_tool.isolate_camera_collection(camera)
|
||||
try:
|
||||
blender.set_active_object(camera)
|
||||
except:
|
||||
raise CameraNotAvailableError()
|
||||
drawing_tool.activate_drawing(camera)
|
||||
|
||||
|
||||
class CameraNotAvailableError(Exception):
|
||||
pass
|
||||
|
||||
@@ -831,7 +831,7 @@ class Spatial:
|
||||
def can_reference(cls, structure, element): pass
|
||||
def contract_container(cls, container): pass
|
||||
def copy_xy(cls, src_obj, destination_obj): pass
|
||||
def create_new_storey_li(cls, element, level_index): pass
|
||||
def import_spatial_structure(cls, element, level_index): pass
|
||||
def deselect_objects(cls): pass
|
||||
def disable_editing(cls, obj): pass
|
||||
def duplicate_object_and_data(cls, obj): pass
|
||||
|
||||
@@ -30,9 +30,9 @@ class Aggregate(blenderbim.core.tool.Aggregate):
|
||||
related_object = tool.Ifc.get_entity(related_obj)
|
||||
if not relating_object or not related_object:
|
||||
return False
|
||||
if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a("IfcElement"):
|
||||
if relating_obj.data: # See #3973
|
||||
return False
|
||||
if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a(
|
||||
"IfcElement"
|
||||
):
|
||||
return True
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"):
|
||||
@@ -46,6 +46,13 @@ class Aggregate(blenderbim.core.tool.Aggregate):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def has_physical_body_representation(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
if element.is_a("IfcElement") or element.is_a("IfcElementType"): # See 3973
|
||||
if ifcopenshell.util.representation.get_representation(element, "Model", "Body"):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def disable_editing(cls, obj: bpy.types.Object) -> None:
|
||||
obj.BIMObjectAggregateProperties.is_editing = False
|
||||
|
||||
@@ -95,9 +95,12 @@ class Collector(blenderbim.core.tool.Collector):
|
||||
# NOTE: won't allow assigning IfcElements to the IfcProject directly
|
||||
# and some elements might get missing in other viewers if they're don't support displaying
|
||||
# elements without hierarchy
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
try:
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
except blenderbim.core.aggregate.IncompatibleAggregateError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def assign(cls, obj: bpy.types.Object) -> None:
|
||||
|
||||
@@ -1752,6 +1752,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
selected_objects_before = bpy.context.selected_objects
|
||||
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
|
||||
@@ -1839,6 +1841,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
cls.import_camera_props(drawing, camera)
|
||||
|
||||
for obj in selected_objects_before:
|
||||
obj.hide_set(False)
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def get_elements_in_camera_view(
|
||||
cls, camera: bpy.types.Object, objs: list[ifcopenshell.entity_instance]
|
||||
|
||||
@@ -725,11 +725,11 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def should_use_presentation_style_assignment(cls):
|
||||
def should_use_presentation_style_assignment(cls) -> bool:
|
||||
return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment
|
||||
|
||||
@classmethod
|
||||
def get_model_representations(cls):
|
||||
def get_model_representations(cls) -> list[ifcopenshell.entity_instance]:
|
||||
return tool.Ifc.get().by_type("IfcShapeRepresentation")
|
||||
|
||||
@classmethod
|
||||
@@ -761,16 +761,25 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
Ensures that same representations won't be reloaded multiple times.
|
||||
"""
|
||||
objs = obj_or_objs if isinstance(obj_or_objs, Iterable) else [obj_or_objs]
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# Find all objects that use the same representation
|
||||
# as there are possibility that some of them have openings
|
||||
# (each representation with opening has a unique Mesh)
|
||||
# and therefore reloading Mesh of it's type or occurrence
|
||||
# might not be enough.
|
||||
elements = set()
|
||||
for obj in objs:
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
representation = tool.Geometry.resolve_mapped_representation(representation)
|
||||
elements.update(ifcopenshell.util.element.get_elements_by_representation(ifc_file, representation))
|
||||
|
||||
# Filter out unique meshes to avoid
|
||||
# reloading the same representation multiple times.
|
||||
meshes_to_objects: dict[bpy.types.Mesh, bpy.types.Object]
|
||||
meshes_to_objects = dict()
|
||||
for obj in objs:
|
||||
mesh = obj.data
|
||||
if not mesh:
|
||||
continue
|
||||
meshes_to_objects.setdefault(mesh, obj)
|
||||
meshes_to_objects = {(obj:=tool.Ifc.get_object(element)).data: obj for element in elements}
|
||||
|
||||
for obj in meshes_to_objects.values():
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
|
||||
@@ -207,45 +207,79 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
src_obj.location = (destination_obj.location[0], destination_obj.location[1], z)
|
||||
|
||||
@classmethod
|
||||
def load_container_manager(cls):
|
||||
cls.props = bpy.context.scene.BIMSpatialManagerProperties
|
||||
previous_container_index = cls.props.active_container_index
|
||||
cls.props.containers.clear()
|
||||
cls.contracted_containers = json.loads(cls.props.contracted_containers)
|
||||
cls.props.is_container_update_enabled = False
|
||||
parent = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
def load_contained_elements(cls):
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
props.elements.clear()
|
||||
if not (container := props.active_container):
|
||||
return
|
||||
|
||||
for object in ifcopenshell.util.element.get_parts(parent) or []:
|
||||
if object.is_a("IfcSpatialElement") or object.is_a("IfcSpatialStructureElement"):
|
||||
cls.create_new_storey_li(object, 0)
|
||||
cls.props.is_container_update_enabled = True
|
||||
# triggers spatial manager props setup
|
||||
cls.props.active_container_index = min(previous_container_index, len(cls.props.containers) - 1)
|
||||
container = tool.Ifc.get().by_id(container.ifc_definition_id)
|
||||
|
||||
results = {}
|
||||
for element in ifcopenshell.util.element.get_contained(container):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
ifc_class = element.is_a()
|
||||
type_name = element_type.Name or "Unnamed" if element_type else "Untyped"
|
||||
results.setdefault(ifc_class, {}).setdefault(type_name, 0)
|
||||
results[ifc_class][type_name] += 1
|
||||
|
||||
total_elements = 0
|
||||
for ifc_class in sorted(results.keys()):
|
||||
new = props.elements.add()
|
||||
new.name = ifc_class
|
||||
new.is_class = True
|
||||
total = 0
|
||||
for type_name in sorted(results[ifc_class].keys()):
|
||||
new2 = props.elements.add()
|
||||
new2.name = type_name
|
||||
new2.is_type = True
|
||||
new2.total = results[ifc_class][type_name]
|
||||
total += new2.total
|
||||
new.total = total
|
||||
total_elements += total
|
||||
|
||||
props.total_elements = total_elements
|
||||
|
||||
@classmethod
|
||||
def create_new_storey_li(cls, element, level_index):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
new = cls.props.containers.add()
|
||||
new.name = element.Name or "Unnamed"
|
||||
new.long_name = element.LongName or ""
|
||||
new.has_decomposition = bool(element.IsDecomposedBy)
|
||||
new.ifc_definition_id = element.id()
|
||||
new.elevation = ifcopenshell.util.placement.get_storey_elevation(element) * si_conversion
|
||||
def load_container_manager(cls):
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
previous_container_index = props.active_container_index
|
||||
props.containers.clear()
|
||||
cls.contracted_containers = json.loads(props.contracted_containers)
|
||||
props.is_container_update_enabled = False
|
||||
parent = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
|
||||
for subelement in ifcopenshell.util.element.get_parts(parent) or []:
|
||||
if subelement.is_a("IfcSpatialElement") or subelement.is_a("IfcSpatialStructureElement"):
|
||||
cls.import_spatial_structure(subelement, 0)
|
||||
props.is_container_update_enabled = True
|
||||
# triggers spatial manager props setup
|
||||
props.active_container_index = min(previous_container_index, len(props.containers) - 1)
|
||||
|
||||
@classmethod
|
||||
def import_spatial_structure(cls, element, level_index):
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
new = props.containers.add()
|
||||
new.ifc_class = element.is_a()
|
||||
new.name = element.Name or "Unnamed"
|
||||
new.description = element.Description or ""
|
||||
new.long_name = element.LongName or ""
|
||||
new.elevation = ifcopenshell.util.placement.get_storey_elevation(element)
|
||||
new.is_expanded = element.id() not in cls.contracted_containers
|
||||
new.level_index = level_index
|
||||
if new.has_decomposition:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_object in ifcopenshell.util.element.get_parts(element) or []:
|
||||
if related_object.is_a("IfcSpatialElement") or related_object.is_a("IfcSpatialStructureElement"):
|
||||
cls.create_new_storey_li(related_object, level_index + 1)
|
||||
children = ifcopenshell.util.element.get_parts(element)
|
||||
new.has_children = bool(children)
|
||||
new.ifc_definition_id = element.id()
|
||||
if new.is_expanded:
|
||||
for child in children or []:
|
||||
cls.import_spatial_structure(child, level_index + 1)
|
||||
|
||||
@classmethod
|
||||
def edit_container_attributes(cls, entity):
|
||||
# TODO
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
name = bpy.context.scene.BIMSpatialManagerProperties.container_name
|
||||
name = bpy.context.scene.BIMProjectTreeProperties.container_name
|
||||
if name != entity.Name:
|
||||
cls.edit_container_name(entity, name)
|
||||
|
||||
@@ -255,21 +289,21 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def get_active_container(cls):
|
||||
props = bpy.context.scene.BIMSpatialManagerProperties
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
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):
|
||||
props = bpy.context.scene.BIMSpatialManagerProperties
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
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):
|
||||
props = bpy.context.scene.BIMSpatialManagerProperties
|
||||
props = bpy.context.scene.BIMProjectTreeProperties
|
||||
contracted_containers = json.loads(props.contracted_containers)
|
||||
contracted_containers.remove(container.id())
|
||||
props.contracted_containers = json.dumps(contracted_containers)
|
||||
|
||||
@@ -36,7 +36,7 @@ Some of these add-ons are not shipped with Blender:
|
||||
import GIS data, grab elevation data from the web, and generate TINs from
|
||||
survey points and contours.
|
||||
- `Ladybug Tools for Blender
|
||||
<https://github.com/Andrej730/ladybug-blender/releases/download/ladybug-blender-240419/ladybug-blender-240419.zip>`__ - Ladybug Tools
|
||||
<https://github.com/ladybug-tools/ladybug-blender/releases/download/ladybug-blender-240529/ladybug-blender-240529.zip>`__ - Ladybug Tools
|
||||
is an extension of Sverchok for environmental analysis and building physics
|
||||
simulation. It allows analysis of solar, daylight, energy, and CFD.
|
||||
- `Topologic <https://topologic.app/>`__ - Perform spatial and topological
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.api.context
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from test.bim.bootstrap import NewFile
|
||||
@@ -92,16 +95,24 @@ class TestCanAggregate(NewFile):
|
||||
subelement_obj = bpy.data.objects.new("Object", None)
|
||||
assert subject.can_aggregate(element_obj, subelement_obj) is False
|
||||
|
||||
def test_aggregates_with_meshes_are_invalid(self):
|
||||
|
||||
class TestHasPhysicalBodyRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
element = ifc.createIfcElementAssembly()
|
||||
element_obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
||||
tool.Ifc.link(element, element_obj)
|
||||
subelement = ifc.createIfcBeam()
|
||||
subelement_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(subelement, subelement_obj)
|
||||
assert subject.can_aggregate(element_obj, subelement_obj) is False
|
||||
assert subject.has_physical_body_representation(element) is False
|
||||
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
|
||||
ifcopenshell.api.unit.assign_unit(ifc)
|
||||
context = ifcopenshell.api.context.add_context(ifc, context_type="Model")
|
||||
body = ifcopenshell.api.context.add_context(
|
||||
ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=context
|
||||
)
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc)
|
||||
origin = builder.create_axis2_placement_3d()
|
||||
block = ifc.createIfcCsgSolid(ifc.createIfcBlock(origin, 200, 200, 200))
|
||||
rep = builder.get_representation(context=body, items=[block])
|
||||
ifcopenshell.api.geometry.assign_representation(ifc, product=element, representation=rep)
|
||||
assert subject.has_physical_body_representation(element) is True
|
||||
|
||||
|
||||
class TestDisableEditing(NewFile):
|
||||
|
||||
@@ -24,8 +24,8 @@ import test.bim.bootstrap
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.qto.calculator as calculator
|
||||
from blenderbim.tool.qto import Qto as subject
|
||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||
|
||||
|
||||
class TestImplementsTool(test.bim.bootstrap.NewFile):
|
||||
@@ -45,48 +45,6 @@ class TestSetQtoResult(test.bim.bootstrap.NewFile):
|
||||
assert bpy.context.scene.BIMQtoProperties.qto_result == "123.457"
|
||||
|
||||
|
||||
class TestGetApplicableQuantityNames(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
schema = ifc.schema
|
||||
properties_templates = (
|
||||
ifcopenshell.util.pset.PsetQto(schema)
|
||||
.get_by_name("Qto_WallBaseQuantities")
|
||||
.get_info()["HasPropertyTemplates"]
|
||||
)
|
||||
applicable_quantity_names = [a.Name for a in properties_templates]
|
||||
assert subject.get_applicable_quantity_names("Qto_WallBaseQuantities") == applicable_quantity_names
|
||||
|
||||
|
||||
class TestGetApplicableBaseQuantityName(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
wall = ifc.createIfcWall()
|
||||
assert subject.get_applicable_base_quantity_name(wall) == "Qto_WallBaseQuantities"
|
||||
|
||||
def test_no_quantities(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
product = ifc.by_type("IfcProject")[0]
|
||||
assert subject.get_applicable_base_quantity_name(product) == None
|
||||
|
||||
def test_anomaly_named_quantities(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingElementProxy")
|
||||
# Prioritized over Qto_BodyGeometryValidation.
|
||||
assert subject.get_applicable_base_quantity_name(product) == "Qto_BuildingElementProxyQuantities"
|
||||
|
||||
def test_prioritize_base_over_other_qto(self):
|
||||
ifc = ifcopenshell.file(schema="IFC4X3")
|
||||
tool.Ifc.set(ifc)
|
||||
product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
assert subject.get_applicable_base_quantity_name(product) == "Qto_WallBaseQuantities"
|
||||
|
||||
|
||||
class TestGetRoundedValue(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
quantity = 1.2345
|
||||
@@ -97,12 +55,14 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
|
||||
def setup_file(self):
|
||||
self.ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(self.ifc)
|
||||
project = ifcopenshell.api.run("root.create_entity", self.ifc, ifc_class="IfcProject", name="My Project")
|
||||
ifcopenshell.api.run("root.create_entity", self.ifc, ifc_class="IfcProject", name="My Project")
|
||||
|
||||
def setup_units(self, units):
|
||||
ifcopenshell.api.run("unit.assign_unit", self.ifc, **units)
|
||||
|
||||
def calculate_quantities(self, obj):
|
||||
import ifc5d.qto
|
||||
|
||||
context = ifcopenshell.api.run("context.add_context", self.ifc, context_type="Model")
|
||||
bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 0.0), size=2)
|
||||
obj = bpy.context.active_object
|
||||
@@ -115,12 +75,32 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
|
||||
predefined_type="ELEMENTEDWALL",
|
||||
context=context,
|
||||
)
|
||||
calculator = QtoCalculator()
|
||||
base_qto = ifcopenshell.api.run("pset.add_qto", self.ifc, product=element, name="Qto_WallBaseQuantities")
|
||||
quantities = subject.get_calculated_object_quantities(
|
||||
calculator=calculator, qto_name="Qto_WallBaseQuantities", obj=obj
|
||||
)
|
||||
return quantities
|
||||
|
||||
rules = {
|
||||
"calculators": {
|
||||
"Blender": {
|
||||
"IfcWall": {
|
||||
"Qto_WallBaseQuantities": {
|
||||
"GrossFootprintArea": "get_gross_footprint_area",
|
||||
"GrossSideArea": "get_gross_side_area",
|
||||
"GrossVolume": "get_gross_volume",
|
||||
"GrossWeight": "get_gross_weight",
|
||||
"Height": "get_height",
|
||||
"Length": "get_length",
|
||||
"NetFootprintArea": "get_net_footprint_area",
|
||||
"NetSideArea": "get_net_side_area",
|
||||
"NetVolume": "get_net_volume",
|
||||
"NetWeight": "get_net_weight",
|
||||
"Width": "get_width",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
results = ifc5d.qto.quantify(ifc_file, {element}, rules)
|
||||
return {k: round(v, 3) for k, v in results[element]["Qto_WallBaseQuantities"].items() if v is not None}
|
||||
|
||||
def test_meters_project_unit(self):
|
||||
self.setup_file()
|
||||
@@ -186,35 +166,6 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
|
||||
assert quantities["NetVolume"] == 282.517
|
||||
|
||||
|
||||
class TestAddObjectBaseQto(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
project = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject", name="My Project")
|
||||
context = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
|
||||
bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 0.0), size=2)
|
||||
obj = bpy.context.active_object
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcWall",
|
||||
predefined_type="ELEMENTEDWALL",
|
||||
context=context,
|
||||
)
|
||||
assert subject.add_object_base_qto(obj).Name == "Qto_WallBaseQuantities"
|
||||
|
||||
|
||||
class TestAddProductBaseQto(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
wall = ifc.createIfcWall()
|
||||
base_qto = subject.add_product_base_qto(wall)
|
||||
assert base_qto.Name == "Qto_WallBaseQuantities"
|
||||
|
||||
|
||||
class TestGetBaseQto(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -236,46 +187,6 @@ class TestGetBaseQto(test.bim.bootstrap.NewFile):
|
||||
product = tool.Ifc.get_entity(wall_obj)
|
||||
assert not subject.get_base_qto(product) == True
|
||||
|
||||
def test_anomaly_named_quantities(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingElementProxy")
|
||||
tool.Ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name="EQto_BodyGeometryValidation",
|
||||
)
|
||||
tool.Ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name="Qto_BuildingElementProxyQuantities",
|
||||
)
|
||||
# Prioritized over Qto_BodyGeometryValidation.
|
||||
base_qto_name = subject.get_base_qto(product).Name
|
||||
assert base_qto_name == "Qto_BuildingElementProxyQuantities"
|
||||
# Ensure methods are in sync.
|
||||
assert base_qto_name == subject.get_applicable_base_quantity_name(product)
|
||||
|
||||
def test_prioritize_base_over_other_qto(self):
|
||||
ifc = ifcopenshell.file(schema="IFC4X3")
|
||||
tool.Ifc.set(ifc)
|
||||
product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||
tool.Ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name="Qto_BodyGeometryValidation",
|
||||
)
|
||||
tool.Ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name="Qto_WallBaseQuantities",
|
||||
)
|
||||
# Prioritized over Qto_BodyGeometryValidation.
|
||||
base_qto_name = subject.get_base_qto(product).Name
|
||||
assert base_qto_name == "Qto_WallBaseQuantities"
|
||||
# Ensure methods are in sync.
|
||||
assert base_qto_name == subject.get_applicable_base_quantity_name(product)
|
||||
|
||||
|
||||
class TestGetRelatedCostItemQuantities(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
|
||||
@@ -76,7 +76,7 @@ class SI2ProjectUnitConverter:
|
||||
"IfcLengthMeasure": "METRE",
|
||||
"IfcMassMeasure": "GRAM",
|
||||
"IfcTimeMeasure": "SECOND",
|
||||
"IfcVolumeMeasure": "CUBIE_METRE",
|
||||
"IfcVolumeMeasure": "CUBIC_METRE",
|
||||
}
|
||||
|
||||
def convert(self, value, measure):
|
||||
|
||||
@@ -102,6 +102,7 @@ elements in your filter group based on their criteria.
|
||||
"Material", "Filter", "``material{{=}}{{value}}``", "``material=Foo`` specifies the criteria that elements must have a IfcMaterial assigned directly or indirectly (such as within a layer set). That IfcMaterial must have either a ``Name`` or ``Category`` attribute with a value of ``Foo``."
|
||||
"Classification", "Filter", "``classification{{=}}{{value}}``", "``classification=Foo`` specifies the criteria that elements must have an IfcClassificationReference with an ``Identification`` attribute with a value of ``Foo``."
|
||||
"Location", "Filter", "``location{{=}}{{value}}``", "``location=Foo`` specifies the criteria that elements must be contained directly or indirectly in a spatial element with a ``Name`` attribute with a value of ``Foo``."
|
||||
"Parent", "Filter", "``parent{{=}}{{value}}``", "``parent=Foo`` specifies the criteria that elements must be a direct or indirect child in the spatial hierarchy to an element with a ``Name`` attribute with a value of ``Foo``."
|
||||
"Query", "Filter", "``query:{{keys}}{{=}}{{value}}``", "``query:types.count=0`` specifies the criteria that elements must have zero type occurrences. The query keys corresponds to the syntax used in the `Getting element values`_ section"
|
||||
|
||||
When you specify a filter with a ``{{=}}`` check, you can choose from one of
|
||||
@@ -183,6 +184,7 @@ Valid keys are:
|
||||
"``storey``", "Gets the first IfcBuildingStorey spatial element that an element is contained in."
|
||||
"``building``", "Gets the first IfcBuilding spatial element that an element is contained in."
|
||||
"``site``", "Gets the first IccSite spatial element that an element is contained in."
|
||||
"``parent``", "Gets the parent element in the spatial hierarchy."
|
||||
"``material`` or ``mat``", "Gets the assigned material, which may be a material set."
|
||||
"``item`` or ``i``", "If the previous key returns a material set, gets the relevant material set items"
|
||||
"``materials`` or ``mats``", "Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element"
|
||||
|
||||
@@ -28,3 +28,8 @@ from .assign_object import assign_object
|
||||
from .unassign_object import unassign_object
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_object",
|
||||
"unassign_object",
|
||||
]
|
||||
|
||||
@@ -27,3 +27,7 @@ from .. import wrap_usecases
|
||||
from .edit_attributes import edit_attributes
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"edit_attributes",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,10 @@ from .edit_attributes import edit_attributes
|
||||
from .remove_boundary import remove_boundary
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_connection_geometry",
|
||||
"copy_boundary",
|
||||
"edit_attributes",
|
||||
"remove_boundary",
|
||||
]
|
||||
|
||||
@@ -36,3 +36,12 @@ from .remove_classification import remove_classification
|
||||
from .remove_reference import remove_reference
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_classification",
|
||||
"add_reference",
|
||||
"edit_classification",
|
||||
"edit_reference",
|
||||
"remove_classification",
|
||||
"remove_reference",
|
||||
]
|
||||
|
||||
@@ -34,3 +34,15 @@ from .remove_metric import remove_metric
|
||||
from .unassign_constraint import unassign_constraint
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_metric",
|
||||
"add_metric_reference",
|
||||
"add_objective",
|
||||
"assign_constraint",
|
||||
"edit_metric",
|
||||
"edit_objective",
|
||||
"remove_constraint",
|
||||
"remove_metric",
|
||||
"unassign_constraint",
|
||||
]
|
||||
|
||||
@@ -31,3 +31,9 @@ from .edit_context import edit_context
|
||||
from .remove_context import remove_context
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_context",
|
||||
"edit_context",
|
||||
"remove_context",
|
||||
]
|
||||
|
||||
@@ -27,3 +27,8 @@ from .assign_control import assign_control
|
||||
from .unassign_control import unassign_control
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_control",
|
||||
"unassign_control",
|
||||
]
|
||||
|
||||
@@ -46,3 +46,25 @@ from .remove_cost_value import remove_cost_value
|
||||
from .unassign_cost_item_quantity import unassign_cost_item_quantity
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_cost_item",
|
||||
"add_cost_item_quantity",
|
||||
"add_cost_schedule",
|
||||
"add_cost_value",
|
||||
"assign_cost_item_quantity",
|
||||
"assign_cost_value",
|
||||
"calculate_cost_item_resource_value",
|
||||
"copy_cost_item",
|
||||
"copy_cost_item_values",
|
||||
"edit_cost_item",
|
||||
"edit_cost_item_quantity",
|
||||
"edit_cost_schedule",
|
||||
"edit_cost_value",
|
||||
"edit_cost_value_formula",
|
||||
"remove_cost_item",
|
||||
"remove_cost_item_quantity",
|
||||
"remove_cost_schedule",
|
||||
"remove_cost_value",
|
||||
"unassign_cost_item_quantity",
|
||||
]
|
||||
|
||||
@@ -35,3 +35,14 @@ from .remove_reference import remove_reference
|
||||
from .unassign_document import unassign_document
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_information",
|
||||
"add_reference",
|
||||
"assign_document",
|
||||
"edit_information",
|
||||
"edit_reference",
|
||||
"remove_information",
|
||||
"remove_reference",
|
||||
"unassign_document",
|
||||
]
|
||||
|
||||
@@ -28,3 +28,9 @@ from .edit_text_literal import edit_text_literal
|
||||
from .unassign_product import unassign_product
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_product",
|
||||
"edit_text_literal",
|
||||
"unassign_product",
|
||||
]
|
||||
|
||||
@@ -61,3 +61,28 @@ from .remove_representation import remove_representation
|
||||
from .unassign_representation import unassign_representation
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_axis_representation",
|
||||
"add_boolean",
|
||||
"add_door_representation",
|
||||
"add_footprint_representation",
|
||||
"add_mesh_representation",
|
||||
"add_profile_representation",
|
||||
"add_railing_representation",
|
||||
"add_representation",
|
||||
"add_slab_representation",
|
||||
"add_wall_representation",
|
||||
"add_window_representation",
|
||||
"assign_representation",
|
||||
"connect_element",
|
||||
"connect_path",
|
||||
"create_2pt_wall",
|
||||
"disconnect_element",
|
||||
"disconnect_path",
|
||||
"edit_object_placement",
|
||||
"map_representation",
|
||||
"remove_boolean",
|
||||
"remove_representation",
|
||||
"unassign_representation",
|
||||
]
|
||||
|
||||
@@ -29,3 +29,9 @@ from .edit_georeferencing import edit_georeferencing
|
||||
from .remove_georeferencing import remove_georeferencing
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_georeferencing",
|
||||
"edit_georeferencing",
|
||||
"remove_georeferencing",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,9 @@ from .create_grid_axis import create_grid_axis
|
||||
from .remove_grid_axis import remove_grid_axis
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"create_axis_curve",
|
||||
"create_grid_axis",
|
||||
"remove_grid_axis",
|
||||
]
|
||||
|
||||
@@ -32,3 +32,12 @@ from .unassign_group import unassign_group
|
||||
from .update_group_products import update_group_products
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_group",
|
||||
"assign_group",
|
||||
"edit_group",
|
||||
"remove_group",
|
||||
"unassign_group",
|
||||
"update_group_products",
|
||||
]
|
||||
|
||||
@@ -33,3 +33,11 @@ from .remove_layer import remove_layer
|
||||
from .unassign_layer import unassign_layer
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_layer",
|
||||
"assign_layer",
|
||||
"edit_layer",
|
||||
"remove_layer",
|
||||
"unassign_layer",
|
||||
]
|
||||
|
||||
@@ -34,3 +34,14 @@ from .remove_reference import remove_reference
|
||||
from .unassign_reference import unassign_reference
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_library",
|
||||
"add_reference",
|
||||
"assign_reference",
|
||||
"edit_library",
|
||||
"edit_reference",
|
||||
"remove_library",
|
||||
"remove_reference",
|
||||
"unassign_reference",
|
||||
]
|
||||
|
||||
@@ -58,3 +58,30 @@ from .reorder_set_item import reorder_set_item
|
||||
from .unassign_material import unassign_material
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_constituent",
|
||||
"add_layer",
|
||||
"add_list_item",
|
||||
"add_material",
|
||||
"add_material_set",
|
||||
"add_profile",
|
||||
"assign_material",
|
||||
"assign_profile",
|
||||
"copy_material",
|
||||
"edit_assigned_material",
|
||||
"edit_constituent",
|
||||
"edit_layer",
|
||||
"edit_layer_usage",
|
||||
"edit_material",
|
||||
"edit_profile",
|
||||
"edit_profile_usage",
|
||||
"remove_constituent",
|
||||
"remove_layer",
|
||||
"remove_list_item",
|
||||
"remove_material",
|
||||
"remove_material_set",
|
||||
"remove_profile",
|
||||
"reorder_set_item",
|
||||
"unassign_material",
|
||||
]
|
||||
|
||||
@@ -34,3 +34,10 @@ from .reorder_nesting import reorder_nesting
|
||||
from .unassign_object import unassign_object
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_object",
|
||||
"change_nest",
|
||||
"reorder_nesting",
|
||||
"unassign_object",
|
||||
]
|
||||
|
||||
@@ -50,3 +50,29 @@ from .unassign_actor import unassign_actor
|
||||
from .update_owner_history import update_owner_history
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_actor",
|
||||
"add_address",
|
||||
"add_application",
|
||||
"add_organisation",
|
||||
"add_person",
|
||||
"add_person_and_organisation",
|
||||
"add_role",
|
||||
"assign_actor",
|
||||
"create_owner_history",
|
||||
"edit_actor",
|
||||
"edit_address",
|
||||
"edit_organisation",
|
||||
"edit_person",
|
||||
"edit_role",
|
||||
"remove_actor",
|
||||
"remove_address",
|
||||
"remove_application",
|
||||
"remove_organisation",
|
||||
"remove_person",
|
||||
"remove_person_and_organisation",
|
||||
"remove_role",
|
||||
"unassign_actor",
|
||||
"update_owner_history",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,11 @@ from .edit_profile import edit_profile
|
||||
from .remove_profile import remove_profile
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_arbitrary_profile",
|
||||
"add_arbitrary_profile_with_voids",
|
||||
"add_parameterized_profile",
|
||||
"edit_profile",
|
||||
"remove_profile",
|
||||
]
|
||||
|
||||
@@ -33,3 +33,10 @@ from .create_file import create_file
|
||||
from .unassign_declaration import unassign_declaration
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"append_asset",
|
||||
"assign_declaration",
|
||||
"create_file",
|
||||
"unassign_declaration",
|
||||
]
|
||||
|
||||
@@ -31,3 +31,11 @@ from .edit_qto import edit_qto
|
||||
from .remove_pset import remove_pset
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_pset",
|
||||
"add_qto",
|
||||
"edit_pset",
|
||||
"edit_qto",
|
||||
"remove_pset",
|
||||
]
|
||||
|
||||
@@ -33,3 +33,12 @@ from .remove_prop_template import remove_prop_template
|
||||
from .remove_pset_template import remove_pset_template
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_prop_template",
|
||||
"add_pset_template",
|
||||
"edit_prop_template",
|
||||
"edit_pset_template",
|
||||
"remove_prop_template",
|
||||
"remove_pset_template",
|
||||
]
|
||||
|
||||
@@ -38,3 +38,18 @@ from .remove_resource_quantity import remove_resource_quantity
|
||||
from .unassign_resource import unassign_resource
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_resource",
|
||||
"add_resource_quantity",
|
||||
"add_resource_time",
|
||||
"assign_resource",
|
||||
"calculate_resource_usage",
|
||||
"calculate_resource_work",
|
||||
"edit_resource",
|
||||
"edit_resource_quantity",
|
||||
"edit_resource_time",
|
||||
"remove_resource",
|
||||
"remove_resource_quantity",
|
||||
"unassign_resource",
|
||||
]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.resource
|
||||
|
||||
|
||||
def add_resource_quantity(
|
||||
@@ -66,6 +67,14 @@ def add_resource_quantity(
|
||||
"""
|
||||
settings = {"resource": resource, "ifc_class": ifc_class}
|
||||
|
||||
resource_type = resource.is_a()
|
||||
supported_quantities = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES[resource_type]
|
||||
if ifc_class not in supported_quantities:
|
||||
raise ValueError(
|
||||
f"Resource type '{resource_type}' does not support quantity type '{ifc_class}'. "
|
||||
f"Supported quantities: {','.join(supported_quantities)}"
|
||||
)
|
||||
|
||||
quantity = file.create_entity(settings["ifc_class"], Name="Unnamed")
|
||||
# 3 IfcPhysicalSimpleQuantity Value
|
||||
if settings["ifc_class"] == "IfcQuantityCount":
|
||||
|
||||
@@ -74,8 +74,9 @@ def remove_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_insta
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
if settings["resource"].Usage:
|
||||
file.remove(settings["resource"].Usage)
|
||||
# Usage was added in IFC4.
|
||||
if usage := getattr(settings["resource"], "Usage", None):
|
||||
file.remove(usage)
|
||||
if settings["resource"].BaseQuantity:
|
||||
ifcopenshell.api.run(
|
||||
"resource.remove_resource_quantity",
|
||||
|
||||
@@ -33,3 +33,10 @@ from .reassign_class import reassign_class
|
||||
from .remove_product import remove_product
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"copy_class",
|
||||
"create_entity",
|
||||
"reassign_class",
|
||||
"remove_product",
|
||||
]
|
||||
|
||||
@@ -67,3 +67,44 @@ from .unassign_recurrence_pattern import unassign_recurrence_pattern
|
||||
from .unassign_sequence import unassign_sequence
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_task",
|
||||
"add_task_time",
|
||||
"add_time_period",
|
||||
"add_work_calendar",
|
||||
"add_work_plan",
|
||||
"add_work_schedule",
|
||||
"add_work_time",
|
||||
"assign_lag_time",
|
||||
"assign_process",
|
||||
"assign_product",
|
||||
"assign_recurrence_pattern",
|
||||
"assign_sequence",
|
||||
"assign_workplan",
|
||||
"calculate_task_duration",
|
||||
"cascade_schedule",
|
||||
"create_baseline",
|
||||
"duplicate_task",
|
||||
"edit_lag_time",
|
||||
"edit_recurrence_pattern",
|
||||
"edit_sequence",
|
||||
"edit_task",
|
||||
"edit_task_time",
|
||||
"edit_work_calendar",
|
||||
"edit_work_plan",
|
||||
"edit_work_schedule",
|
||||
"edit_work_time",
|
||||
"recalculate_schedule",
|
||||
"remove_task",
|
||||
"remove_time_period",
|
||||
"remove_work_calendar",
|
||||
"remove_work_plan",
|
||||
"remove_work_schedule",
|
||||
"remove_work_time",
|
||||
"unassign_lag_time",
|
||||
"unassign_process",
|
||||
"unassign_product",
|
||||
"unassign_recurrence_pattern",
|
||||
"unassign_sequence",
|
||||
]
|
||||
|
||||
@@ -29,3 +29,10 @@ from .reference_structure import reference_structure
|
||||
from .unassign_container import unassign_container
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_container",
|
||||
"dereference_structure",
|
||||
"reference_structure",
|
||||
"unassign_container",
|
||||
]
|
||||
|
||||
@@ -46,3 +46,27 @@ from .remove_structural_load_group import remove_structural_load_group
|
||||
from .unassign_structural_analysis_model import unassign_structural_analysis_model
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_structural_activity",
|
||||
"add_structural_analysis_model",
|
||||
"add_structural_boundary_condition",
|
||||
"add_structural_load",
|
||||
"add_structural_load_case",
|
||||
"add_structural_load_group",
|
||||
"add_structural_member_connection",
|
||||
"assign_structural_analysis_model",
|
||||
"edit_structural_analysis_model",
|
||||
"edit_structural_boundary_condition",
|
||||
"edit_structural_connection_cs",
|
||||
"edit_structural_item_axis",
|
||||
"edit_structural_load",
|
||||
"edit_structural_load_case",
|
||||
"remove_structural_analysis_model",
|
||||
"remove_structural_boundary_condition",
|
||||
"remove_structural_connection_condition",
|
||||
"remove_structural_load",
|
||||
"remove_structural_load_case",
|
||||
"remove_structural_load_group",
|
||||
"unassign_structural_analysis_model",
|
||||
]
|
||||
|
||||
@@ -38,3 +38,18 @@ from .unassign_material_style import unassign_material_style
|
||||
from .unassign_representation_styles import unassign_representation_styles
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_style",
|
||||
"add_surface_style",
|
||||
"add_surface_textures",
|
||||
"assign_material_style",
|
||||
"assign_representation_styles",
|
||||
"edit_presentation_style",
|
||||
"edit_surface_style",
|
||||
"remove_style",
|
||||
"remove_styled_representation",
|
||||
"remove_surface_style",
|
||||
"unassign_material_style",
|
||||
"unassign_representation_styles",
|
||||
]
|
||||
|
||||
@@ -39,3 +39,18 @@ from .unassign_port import unassign_port
|
||||
from .unassign_system import unassign_system
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_port",
|
||||
"add_system",
|
||||
"assign_flow_control",
|
||||
"assign_port",
|
||||
"assign_system",
|
||||
"connect_port",
|
||||
"disconnect_port",
|
||||
"edit_system",
|
||||
"remove_system",
|
||||
"unassign_flow_control",
|
||||
"unassign_port",
|
||||
"unassign_system",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,9 @@ from .map_type_representations import map_type_representations
|
||||
from .unassign_type import unassign_type
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_type",
|
||||
"map_type_representations",
|
||||
"unassign_type",
|
||||
]
|
||||
|
||||
@@ -36,3 +36,16 @@ from .remove_unit import remove_unit
|
||||
from .unassign_unit import unassign_unit
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_context_dependent_unit",
|
||||
"add_conversion_based_unit",
|
||||
"add_monetary_unit",
|
||||
"add_si_unit",
|
||||
"assign_unit",
|
||||
"edit_derived_unit",
|
||||
"edit_monetary_unit",
|
||||
"edit_named_unit",
|
||||
"remove_unit",
|
||||
"unassign_unit",
|
||||
]
|
||||
|
||||
@@ -31,3 +31,10 @@ from .remove_filling import remove_filling
|
||||
from .remove_opening import remove_opening
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_filling",
|
||||
"add_opening",
|
||||
"remove_filling",
|
||||
"remove_opening",
|
||||
]
|
||||
|
||||
@@ -332,7 +332,7 @@ class entity_instance:
|
||||
self.wrapped_data.setArgumentAsNull(idx)
|
||||
except RuntimeError as e:
|
||||
if e.args == ("Attribute not set",):
|
||||
raise ValueError(
|
||||
raise TypeError(
|
||||
"attribute '%s' is not optional for entity instance of type '%s'"
|
||||
% (self.wrapped_data.get_argument_name(idx), self.wrapped_data.is_a(True))
|
||||
)
|
||||
|
||||
@@ -648,6 +648,8 @@ def get_styles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entit
|
||||
return styles
|
||||
|
||||
|
||||
# TODO: ifc_file argument is unnecessary for some methods now
|
||||
# since we have entity_instance.file, so we can deprecate it.
|
||||
def get_elements_by_material(
|
||||
ifc_file: ifcopenshell.file, material: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
@@ -1041,7 +1043,9 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
|
||||
- Voiding: the opening voids another physical element, such as a hole in a wall
|
||||
|
||||
:param element: Any physical or spatial element in the tree
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: Its parent. This must exist for any valid file, or None if we've reached the IfcProject.
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1065,7 +1069,9 @@ def get_filled_void(element: ifcopenshell.entity_instance) -> Union[ifcopenshell
|
||||
Examples include windows and doors which fill a opening inside a wall.
|
||||
|
||||
:param element: The building element, typically a window or door
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: The IfcOpeningElement that it is filling
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1084,7 +1090,9 @@ def get_voided_element(element: ifcopenshell.entity_instance) -> Union[ifcopensh
|
||||
For all valid models, this should never return None.
|
||||
|
||||
:param element: The IfcOpeningElement
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: The building element, such as a wall or slab
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1102,7 +1110,9 @@ def get_aggregate(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.e
|
||||
Retrieves the aggregate parent of an element.
|
||||
|
||||
:param element: The IFC element
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: The aggregate of the element
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1116,14 +1126,14 @@ def get_aggregate(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.e
|
||||
return decomposes[0].RelatingObject
|
||||
|
||||
|
||||
def get_nest(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance]:
|
||||
def get_nest(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""
|
||||
Retrieves the nest parent of an element.
|
||||
|
||||
:param element: The IFC element
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: The nested whole of the element
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1159,6 +1169,28 @@ def get_parts(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity
|
||||
if (is_decomposed_by := getattr(element, "IsDecomposedBy", None)) is not None and is_decomposed_by:
|
||||
if is_decomposed_by[0].is_a("IfcRelAggregates"):
|
||||
return is_decomposed_by[0].RelatedObjects
|
||||
return []
|
||||
|
||||
|
||||
def get_contained(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Retrieves the contained elements of spatial element.
|
||||
|
||||
:param element: The IFC element
|
||||
:type element: ifcopenshell.entity_instance
|
||||
:return: The parts of the element
|
||||
:rtype: list[ifcopenshell.entity_instance]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
element = file.by_type("IfcBuildingStorey")[0]
|
||||
elements = ifcopenshell.util.element.get_contained(element)
|
||||
"""
|
||||
if (rel := getattr(element, "ContainsElements", None)) is not None and rel:
|
||||
return rel[0].RelatedElements
|
||||
return []
|
||||
|
||||
|
||||
def get_components(element: ifcopenshell.entity_instance, include_ports=False) -> list[ifcopenshell.entity_instance]:
|
||||
@@ -1188,6 +1220,7 @@ def get_components(element: ifcopenshell.entity_instance, include_ports=False) -
|
||||
elif (is_decomposed_by := getattr(element, "IsDecomposedBy", None)) is not None and is_decomposed_by:
|
||||
if is_decomposed_by[0].is_a("IfcRelNests"):
|
||||
return is_decomposed_by[0].RelatedObjects
|
||||
return []
|
||||
|
||||
|
||||
ReferenceData = namedtuple("ReferenceData", "inverse_attribute, rel_class, relating_element_attribute")
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Optional, Union, TypedDict
|
||||
@@ -98,12 +99,12 @@ def resolve_representation(representation: ifcopenshell.entity_instance) -> ifco
|
||||
|
||||
|
||||
class ResolvedItemDict(TypedDict):
|
||||
matrix: np.array
|
||||
matrix: npt.NDArray[np.float64]
|
||||
item: ifcopenshell.entity_instance
|
||||
|
||||
|
||||
def resolve_items(
|
||||
representation: ifcopenshell.entity_instance, matrix: Optional[np.array] = None
|
||||
representation: ifcopenshell.entity_instance, matrix: Optional[npt.NDArray[np.float64]] = None
|
||||
) -> list[ResolvedItemDict]:
|
||||
if matrix is None:
|
||||
matrix = np.eye(4)
|
||||
|
||||
@@ -23,6 +23,20 @@ from typing import Union, Any
|
||||
|
||||
|
||||
PRODUCTIVITY_PSET_DATA = Union[dict[str, Any], None]
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConstructionResource.htm#Table-7.3.3.7.1.3.H
|
||||
RESOURCES_TO_QUANTITIES: dict[str, tuple[str, ...]] = {
|
||||
"IfcCrewResource": ("IfcQuantityTime",),
|
||||
"IfcLaborResource": ("IfcQuantityTime",),
|
||||
"IfcSubContractResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionEquipmentResource": ("IfcQuantityTime",),
|
||||
"IfcConstructionMaterialResource": (
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityWeight",
|
||||
),
|
||||
"IfcConstructionProductResource": ("IfcQuantityCount",),
|
||||
}
|
||||
|
||||
|
||||
def get_productivity(resource: ifcopenshell.entity_instance, should_inherit: bool = True) -> PRODUCTIVITY_PSET_DATA:
|
||||
|
||||
@@ -687,21 +687,45 @@ class FacetTransformer(lark.Transformer):
|
||||
def parent(self, args):
|
||||
comparison, value = args
|
||||
|
||||
def filter_function(element):
|
||||
parents = []
|
||||
result = False
|
||||
if parent := ifcopenshell.util.element.get_parent(element):
|
||||
parents.append(parent)
|
||||
while parents:
|
||||
parent = parents.pop()
|
||||
if self.compare(parent.Name, comparison, value):
|
||||
result = True
|
||||
break
|
||||
if grandparent := ifcopenshell.util.element.get_parent(parent):
|
||||
parents.append(grandparent)
|
||||
return result if comparison == "=" else not result
|
||||
parents = set()
|
||||
for rel in self.file.by_type("IfcRelAggregates"):
|
||||
parent = rel.RelatingObject
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
self.elements = set(filter(filter_function, self.elements))
|
||||
for rel in self.file.by_type("IfcRelContainedInSpatialStructure"):
|
||||
parent = rel.RelatingStructure
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
for rel in self.file.by_type("IfcRelNests"):
|
||||
parent = rel.RelatingObject
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
for rel in self.file.by_type("IfcRelVoidsElement"):
|
||||
parent = rel.RelatingBuildingElement
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
for rel in self.file.by_type("IfcRelVoidsElement"):
|
||||
parent = rel.RelatingBuildingElement
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
for rel in self.file.by_type("IfcRelFillsElement"):
|
||||
parent = rel.RelatingOpeningElement
|
||||
if parent and self.compare(parent.Name, comparison, value):
|
||||
parents.add(parent)
|
||||
|
||||
children = set()
|
||||
for parent in parents:
|
||||
children |= set(ifcopenshell.util.element.get_decomposition(parent))
|
||||
|
||||
if comparison == "=":
|
||||
self.elements = self.elements & children
|
||||
else:
|
||||
self.elements -= children
|
||||
|
||||
def query(self, args):
|
||||
keys, comparison, value = args
|
||||
|
||||
@@ -16,27 +16,45 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.resource
|
||||
import ifcopenshell.util.resource
|
||||
|
||||
|
||||
class TestAddResourceQuantity(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
schema = ifcopenshell.schema_by_name(self.file.schema)
|
||||
quantity_types = [t.name() for t in schema.declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()]
|
||||
self.file.create_entity("IfcProject") # add_resource
|
||||
resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcCrewResource")
|
||||
resource_types = [t.name() for t in schema.declaration_by_name("IfcConstructionResource").subtypes()]
|
||||
|
||||
for quantity_type in quantity_types:
|
||||
quantity = ifcopenshell.api.run(
|
||||
"resource.add_resource_quantity", self.file, resource=resource, ifc_class=quantity_type
|
||||
)
|
||||
assert quantity.is_a(quantity_type)
|
||||
assert quantity.Name == "Unnamed"
|
||||
assert quantity[3] == 0.0
|
||||
# previous quantity is reassigned and removed
|
||||
assert resource.BaseQuantity == quantity
|
||||
assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 1
|
||||
self.file.create_entity("IfcProject") # add_resource
|
||||
|
||||
for resource_type in resource_types:
|
||||
resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class=resource_type)
|
||||
available_quantities = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES[resource_type]
|
||||
|
||||
for quantity_type in quantity_types:
|
||||
if quantity_type not in available_quantities:
|
||||
with pytest.raises(ValueError):
|
||||
quantity = ifcopenshell.api.resource.add_resource_quantity(
|
||||
self.file, resource=resource, ifc_class=quantity_type
|
||||
)
|
||||
continue
|
||||
else:
|
||||
quantity = ifcopenshell.api.resource.add_resource_quantity(
|
||||
self.file, resource=resource, ifc_class=quantity_type
|
||||
)
|
||||
|
||||
assert quantity.is_a(quantity_type)
|
||||
assert quantity.Name == "Unnamed"
|
||||
assert quantity[3] == 0.0
|
||||
# previous quantity is reassigned and removed
|
||||
assert resource.BaseQuantity == quantity
|
||||
assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 1
|
||||
|
||||
ifcopenshell.api.resource.remove_resource(self.file, resource)
|
||||
|
||||
|
||||
class TestAddResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestAddResourceQuantity):
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# remove_resource tests is partially covered by test_add_resource_quantity.
|
||||
@@ -28,7 +28,7 @@ from typing import Union
|
||||
|
||||
def deprecation_check(test):
|
||||
def new_test(self):
|
||||
assert datetime.now().date() < datetime(2024, 6, 1).date(), "API arguments are completely deprecated"
|
||||
assert datetime.now().date() < datetime(2024, 8, 1).date(), "API arguments are completely deprecated"
|
||||
test(self)
|
||||
|
||||
return new_test
|
||||
|
||||
@@ -16,13 +16,22 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit as subject
|
||||
from math import pi
|
||||
|
||||
|
||||
class TestConvert(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
assert subject.convert(1, None, "METRE", None, "METRE") == 1
|
||||
assert subject.convert(1, None, "METRE", "MILLI", "METRE") == 1000
|
||||
assert subject.convert(1000, "MILLI", "METRE", None, "METRE") == 1
|
||||
assert subject.convert(1, None, "SQUARE_METRE", None, "SQUARE_METRE") == 1
|
||||
assert subject.convert(1, None, "SQUARE_METRE", "MILLI", "SQUARE_METRE") == 1000000
|
||||
assert subject.convert(1, None, "CUBIC_METRE", "MILLI", "CUBIC_METRE") == 1000000000
|
||||
|
||||
|
||||
class TestCalculateUnitScale(test.bootstrap.IFC4):
|
||||
def test_prefix_and_conversion_based_units_are_considered(self):
|
||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
|
||||
Reference in New Issue
Block a user