Compare commits

..

1 Commits

Author SHA1 Message Date
Ryan Schultz 6247fb3cdf keep font scaled correctly when changing drawing width and height 2024-06-01 15:41:46 -05:00
88 changed files with 469 additions and 1189 deletions
@@ -142,7 +142,6 @@ classes = [
ui.BIM_PT_tabs,
# Project overview
ui.BIM_PT_tab_project_info,
ui.BIM_PT_tab_spatial_decomposition,
ui.BIM_PT_tab_project_setup,
ui.BIM_PT_tab_geometry,
ui.BIM_PT_tab_stakeholders,
Binary file not shown.
@@ -37,7 +37,6 @@ import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape
import blenderbim.tool as tool
import blenderbim.core.spatial
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
from itertools import chain, accumulate
from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
@@ -302,7 +301,6 @@ class IfcImporter:
self.setup_viewport_camera()
self.setup_arrays()
self.profile_code("Setup arrays")
blenderbim.core.spatial.import_spatial_decomposition(tool.Spatial)
self.update_progress(100)
bpy.context.window_manager.progress_end()
@@ -1704,7 +1702,6 @@ 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"):
@@ -36,6 +36,7 @@ class AggregateData:
"relating_object_label": cls.get_relating_object_label(),
"has_related_objects": cls.has_related_objects(),
"total_parts": cls.total_parts(),
"ifc_class": cls.ifc_class(),
"total_linked_aggregate": cls.total_linked_aggregate(),
}
cls.is_loaded = True
@@ -67,6 +68,12 @@ class AggregateData:
def has_related_objects(cls) -> bool:
return bool(cls.get_related_objects())
@classmethod
def ifc_class(cls) -> str:
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
return element.is_a()
@classmethod
def total_linked_aggregate(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -55,24 +55,21 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
if not relating_obj:
return
for obj in tool.Blender.get_selected_objects():
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
if obj == relating_obj:
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
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")
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}")
class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
@@ -275,23 +272,23 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
bl_options = {"REGISTER", "UNDO"}
part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"})
part_name: bpy.props.StringProperty(name="Name")
element: bpy.props.IntProperty(options={"HIDDEN"})
obj: bpy.props.StringProperty(options={"HIDDEN"})
def invoke(self, context, event):
self.part_name = "My " + self.part_class.lstrip("Ifc")
return context.window_manager.invoke_props_dialog(self)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) or context.active_object
core.add_part_to_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
tool.Blender,
obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.element)) if self.element else context.active_object,
obj=obj,
part_class=self.part_class,
part_name=self.part_name,
)
tool.Spatial.load_container_manager()
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
@@ -99,6 +99,19 @@ class BIM_PT_aggregate(Panel):
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
ifc_class = AggregateData.data["ifc_class"]
part_class = ""
if ifc_class == "IfcBuilding":
part_class = "IfcBuildingStorey"
elif ifc_class == "IfcSite":
part_class = "IfcBuilding"
elif ifc_class == "IfcProject":
part_class = "IfcSite"
if part_class != "":
op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc"))
op.part_class = part_class
op.obj = context.active_object.name
class BIM_PT_linked_aggregate(Panel):
bl_label = "Linked Aggregates"
@@ -29,9 +29,13 @@ 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
@@ -168,15 +168,12 @@ class BaseDecorator:
def get_camera_width_mm(self):
# Horrific prototype code to ensure bgl draws at drawing scales
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
def is_landscape(render):
return render.resolution_x > render.resolution_y
camera = bpy.context.scene.camera
render = bpy.context.scene.render
if is_landscape(render):
camera_width_model = camera.data.ortho_scale
else:
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
camera_width_model = camera.data.ortho_scale
scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
camera_width_mm = scale * camera_width_model
@@ -415,14 +412,12 @@ class BaseDecorator:
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
mm_to_px = camera_width_px / self.get_camera_width_mm()
# magic_font_scale's default of (0.004118616) is a magic constant number I visually discovered to get the right number.
# 0.004118616 is a magic constant number I visually discovered to get the right number.
# In particular it works only for the OpenGOST font and produces a 2.5mm font size.
# It probably should be dynamically calculated using system.dpi or something.
# font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg
magic_font_scale = bpy.context.scene.DocProperties.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
font_size_px = int(0.004118616 * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1]
blf.size(font_id, font_size_px)
@@ -29,7 +29,6 @@ import subprocess
import numpy as np
import multiprocessing
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.ifcopenshell_wrapper
import ifcopenshell.geom
import ifcopenshell.util.selector
@@ -1519,48 +1518,47 @@ class ActivateDrawing(bpy.types.Operator):
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position.\n\n" + "SHIFT+CLICK activiate drawing without turning objects on/off."
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
drawing: bpy.props.IntProperty()
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
switch_camera_only: bpy.props.BoolProperty(name="Only Changes Camera View", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# keep the viewport position on alt+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.alt:
self.camera_view_point = False
# Only activates the camera view on alt+shift. Does not turn on/off objects in scene
if event.type == "LEFTMOUSE" and event.shift:
self.switch_camera_only = True
return self.execute(context)
def execute(self, context):
drawing = tool.Ifc.get().by_id(self.drawing)
dprops = bpy.context.scene.DocProperties
camera = tool.Drawing.import_drawing(drawing)
if self.switch_camera_only:
tool.Blender.activate_camera(camera)
else:
if not self.camera_view_point:
viewport_position = tool.Blender.get_viewport_position()
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"}
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
tool.Drawing.show_decorations()
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
tool.Drawing.show_decorations()
return {"FINISHED"}
@@ -1640,7 +1638,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.scene.camera.data.BIMCameraProperties
camera_props = context.active_object.data.BIMCameraProperties
# added this part as a temporary fallback
# TODO: should remove it a bit later when projects get more accommodated
@@ -370,7 +370,6 @@ class DocProperties(PropertyGroup):
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
magic_font_scale: bpy.props.FloatProperty(default=0.004118616, name="Font Scale Factor")
class BIMCameraProperties(PropertyGroup):
@@ -596,7 +596,6 @@ 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)
@@ -703,7 +702,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 = "SHIFT+CLICK to expand all material categories"
bl_description = "Expand material category.\n\nSHIFT+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"})
@@ -732,7 +731,7 @@ class ExpandMaterialCategory(bpy.types.Operator):
class ContractMaterialCategory(bpy.types.Operator):
bl_idname = "bim.contract_material_category"
bl_label = "Contract Material Category"
bl_description = "SHIFT+CLICK to contract all material categories"
bl_description = "Contract material category.\n\nSHIFT+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"})
@@ -110,7 +110,7 @@ class BIM_PT_materials(Panel):
elif self.props.editing_material_type == "STYLE":
row = self.layout.row(align=True)
row.prop(self.props, "contexts", text="")
prop_with_search(row, self.props, "styles", text="")
row.prop(self.props, "styles", text="")
row = self.layout.row(align=True)
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
@@ -121,16 +121,12 @@ 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",
}[data_type]
}[arg_info.get("type", "str")]
new_attr.name = arg_name
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
return {"FINISHED"}
@@ -36,7 +36,6 @@ import blenderbim.tool as tool
import blenderbim.core.project as core
import blenderbim.core.context
import blenderbim.core.owner
import blenderbim.core.spatial
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim import import_ifc
@@ -123,7 +122,6 @@ class CreateProject(bpy.types.Operator):
for mat in bpy.data.materials:
bpy.data.materials.remove(mat)
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
blenderbim.core.spatial.import_spatial_decomposition(tool.Spatial)
tool.Blender.register_toolbar()
def rollback(self, data):
@@ -23,7 +23,6 @@ classes = (
operator.CalculateCircleRadius,
operator.CalculateEdgeLengths,
operator.CalculateFaceAreas,
operator.CalculateFormworkArea,
operator.CalculateObjectVolumes,
operator.CalculateSingleQuantity,
operator.PerformQuantityTakeOff,
@@ -84,21 +84,6 @@ 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,16 +87,14 @@ class BIM_PT_qto_simple(bpy.types.Panel):
row = layout.row()
row.prop(props, "qto_result", text="Results")
row = layout.row()
row = layout.row(align=True)
row.operator("bim.calculate_circle_radius")
row = layout.row()
row = layout.row(align=True)
row.operator("bim.calculate_edge_lengths")
row = layout.row()
row = layout.row(align=True)
row.operator("bim.calculate_face_areas")
row = layout.row()
row = layout.row(align=True)
row.operator("bim.calculate_object_volumes")
row = layout.row()
row.operator("bim.calculate_formwork_area")
class BIM_PT_qto_cost(bpy.types.Panel):
@@ -41,7 +41,20 @@ quantitytypes_enum = {}
def setup_quantity_types_enum():
resources = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES
# 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",),
}
for resource, quantities in resources.items():
quantitytypes_enum[resource] = [(q, q, "") for q in quantities]
@@ -22,32 +22,31 @@ 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.ImportSpatialDecomposition,
operator.ReferenceStructure,
operator.RemoveContainer,
operator.SelectContainer,
operator.SelectDecomposedElements,
operator.SelectProduct,
operator.SelectSimilarContainer,
operator.SelectProduct,
operator.LoadContainerManager,
operator.EditContainerAttributes,
operator.AddBuildingStorey,
operator.ContractContainer,
operator.ExpandContainer,
operator.DeleteContainer,
operator.SelectDecomposedElements,
prop.SpatialElement,
prop.Element,
prop.BIMSpatialProperties,
prop.BIMObjectSpatialProperties,
prop.BIMContainer,
prop.BIMSpatialDecompositionProperties,
prop.BIMSpatialManagerProperties,
ui.BIM_PT_spatial,
ui.BIM_UL_containers,
ui.BIM_UL_containers_manager,
ui.BIM_UL_elements,
ui.BIM_PT_spatial_decomposition,
ui.BIM_PT_SpatialManager,
workspace.Hotkey,
)
@@ -57,7 +56,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.BIMSpatialDecompositionProperties = bpy.props.PointerProperty(type=prop.BIMSpatialDecompositionProperties)
bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties)
def unregister():
@@ -65,4 +64,4 @@ def unregister():
bpy.utils.unregister_tool(workspace.SpatialTool)
del bpy.types.Scene.BIMSpatialProperties
del bpy.types.Object.BIMObjectSpatialProperties
del bpy.types.Scene.BIMSpatialDecompositionProperties
del bpy.types.Scene.BIMSpatialManagerProperties
@@ -23,7 +23,6 @@ import ifcopenshell.util.element
def refresh():
SpatialData.is_loaded = False
SpatialDecompositionData.is_loaded = False
class SpatialData:
@@ -32,28 +31,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 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
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
@classmethod
def parent_container_id(cls):
@@ -82,81 +81,3 @@ class SpatialData:
@classmethod
def is_directly_contained(cls):
return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure", False))
class SpatialDecompositionData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"subelement_class": cls.subelement_class(),
}
@classmethod
def subelement_class(cls):
results = []
props = bpy.context.scene.BIMSpatialDecompositionProperties
if not (container := props.active_container):
return results
container_class = tool.Ifc.get().by_id(container.ifc_definition_id).is_a()
if tool.Ifc.get_schema() == "IFC2X3":
results = {
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
"IfcProject": ["IfcBuilding", "IfcSite", "IfcSpace"],
"IfcSite": ["IfcBuilding", "IfcSite", "IfcSpace"],
"IfcSpace": ["IfcSpace"],
}[container_class]
elif tool.Ifc.get_schema() == "IFC4":
results = {
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
"IfcExternalSpatialElement": ["IfcExternalSpatialElement"],
"IfcProject": ["IfcBuilding", "IfcExternalSpatialElement", "IfcSite", "IfcSpace"],
"IfcSite": ["IfcBuilding", "IfcExternalSpatialElement", "IfcSite", "IfcSpace"],
"IfcSpace": ["IfcSpace"],
}[container_class]
elif tool.Ifc.get_schema() == "IFC4X3":
results = {
"IfcBridge": ["IfcBridge", "IfcBridgePart", "IfcSpace"],
"IfcBridgePart": ["IfcSpace"],
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
"IfcExternalSpatialElement": ["IfcExternalSpatialElement"],
"IfcFacility": ["IfcFacility", "IfcFacilityPartCommon", "IfcSpace"],
"IfcFacilityPartCommon": ["IfcSpace"],
"IfcMarineFacility": ["IfcMarineFacility", "IfcMarinePart", "IfcSpace"],
"IfcMarinePart": ["IfcSpace"],
"IfcProject": [
"IfcBridge",
"IfcBuilding",
"IfcExternalSpatialElement",
"IfcFacility",
"IfcMarineFacility",
"IfcRailway",
"IfcRoad",
"IfcSite",
"IfcSpace",
],
"IfcRailway": ["IfcRailway", "IfcRailwayPart", "IfcSpace"],
"IfcRailwayPart": ["IfcSpace"],
"IfcRoad": ["IfcRoad", "IfcRoadPart", "IfcSpace"],
"IfcRoadPart": ["IfcSpace"],
"IfcSite": [
"IfcBridge",
"IfcBuilding",
"IfcExternalSpatialElement",
"IfcFacility",
"IfcMarineFacility",
"IfcRailway",
"IfcRoad",
"IfcSite",
"IfcSpace",
],
"IfcSpace": ["IfcSpace"],
}[container_class]
return [(r, r, "") for r in results]
@@ -175,13 +175,13 @@ class SelectProduct(bpy.types.Operator):
return {"FINISHED"}
class ImportSpatialDecomposition(bpy.types.Operator):
bl_idname = "bim.import_spatial_decomposition"
class LoadContainerManager(bpy.types.Operator):
bl_idname = "bim.load_container_manager"
bl_label = "Load Container Manager"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
core.import_spatial_decomposition(tool.Spatial)
core.load_container_manager(tool.Spatial)
return {"FINISHED"}
@@ -227,6 +227,26 @@ 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"
@@ -18,7 +18,7 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.spatial.data import SpatialDecompositionData
from blenderbim.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,32 +31,32 @@ from bpy.props import (
CollectionProperty,
)
import blenderbim.tool as tool
import blenderbim.core.geometry
import ifcopenshell
def get_subelement_class(self, context):
if not SpatialDecompositionData.is_loaded:
SpatialDecompositionData.load()
return SpatialDecompositionData.data["subelement_class"]
def update_elevation(self, context):
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)
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
def update_active_container_index(self, context):
SpatialDecompositionData.data["subelement_class"] = SpatialDecompositionData.subelement_class()
tool.Spatial.load_contained_elements()
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
def update_relating_container_from_object(self, context):
@@ -104,35 +104,20 @@ class BIMObjectSpatialProperties(PropertyGroup):
class BIMContainer(PropertyGroup):
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)
name: StringProperty(name="Name", update=updateContainerName)
elevation: FloatProperty(name="Elevation", subtype="DISTANCE")
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 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 BIMSpatialDecompositionProperties(PropertyGroup):
class BIMSpatialManagerProperties(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)
elements: CollectionProperty(name="Elements", type=Element)
active_element_index: IntProperty(name="Active Element Index")
total_elements: IntProperty(name="Total Elements")
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
@property
def active_container(self):
if self.containers and self.active_container_index < len(self.containers):
return self.containers[self.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
@@ -17,7 +17,8 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel, UIList
from blenderbim.bim.module.spatial.data import SpatialData, SpatialDecompositionData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.spatial.data import SpatialData
import blenderbim.tool as tool
@@ -31,9 +32,14 @@ class BIM_PT_spatial(Panel):
@classmethod
def poll(cls, context):
if not SpatialData.is_loaded:
SpatialData.load()
return SpatialData.data["poll"]
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
def draw(self, context):
if not SpatialData.is_loaded:
@@ -91,49 +97,34 @@ class BIM_UL_containers(UIList):
)
class BIM_PT_spatial_decomposition(Panel):
bl_label = "Spatial Decomposition"
bl_idname = "BIM_PT_spatial_decomposition"
class BIM_PT_SpatialManager(Panel):
bl_label = "Spatial Manager"
bl_idname = "BIM_PT_SpatialManager"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_spatial_decomposition"
bl_options = {"HIDE_HEADER"}
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Ifc.get() and tool.Ifc.schema().name() != "IFC2X3"
def draw(self, context):
if not SpatialDecompositionData.is_loaded:
SpatialDecompositionData.load()
self.props = context.scene.BIMSpatialDecompositionProperties
if self.props.active_container:
row = self.layout.row(align=True)
row.label(
text=f"Active: {self.props.active_container.name}",
icon="OUTLINER_COLLECTION",
)
row.operator("bim.import_spatial_decomposition", icon="FILE_REFRESH", text="")
if self.props.active_container.ifc_class != "IfcProject":
row = self.layout.row(align=True)
row.operator("bim.select_decomposed_elements", icon="HIDE_OFF", text=f"Isolate {self.props.active_container.ifc_class}")
row.operator("bim.delete_container", icon="X", text="").container = (
self.props.active_container.ifc_definition_id
)
row = self.layout.row(align=True)
row.prop(self.props, "subelement_class", text="")
op = row.operator("bim.add_part_to_object", icon="ADD", text="")
op.element = self.props.active_container.ifc_definition_id
op.part_class = self.props.subelement_class
else:
row = self.layout.row(align=True)
row.label(text="Warning: No Active Container", icon="ERROR")
row.operator("bim.import_spatial_decomposition", icon="FILE_REFRESH", text="")
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
self.layout.template_list(
"BIM_UL_containers_manager",
"",
@@ -141,29 +132,13 @@ class BIM_PT_spatial_decomposition(Panel):
"containers",
self.props,
"active_container_index",
rows=10,
)
if not self.props.active_container:
return
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):
@@ -171,57 +146,22 @@ class BIM_UL_containers_manager(UIList):
if item:
row = layout.row(align=True)
self.draw_hierarchy(row, item)
row.prop(item, "name", emboss=False, text="")
if item.long_name:
row.prop(item, "long_name", emboss=False, text="")
col = row.column()
col.alignment = "RIGHT"
col.prop(item, "elevation", emboss=False, text="")
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))
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="BLANK1")
if item.ifc_class == "IfcProject":
row.label(text="", icon="FILE")
elif 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")
elif "Part" in item.ifc_class:
row.label(text="", icon="MOD_FLUID")
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))
row.label(text="", icon="DOT")
@@ -64,10 +64,16 @@ 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)
@@ -145,10 +151,12 @@ 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,7 +24,6 @@ classes = (
operator.AddPresentationStyle,
operator.AddStyle,
operator.AddSurfaceTexture,
operator.AssignStyleToSelected,
operator.BrowseExternalStyle,
operator.RemoveTextureMap,
operator.ChooseTextureMapPath,
@@ -23,7 +23,6 @@ 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
@@ -162,6 +161,13 @@ 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
@@ -952,49 +958,3 @@ 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,8 +65,6 @@ 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")
-1
View File
@@ -328,7 +328,6 @@ def get_tab(self, context):
("SCHEDULING", "Costing and Scheduling", "", "NLA", 6),
("FM", "Facility Management", "", "PACKAGE", 7),
("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8),
None,
("BLENDER", "Blender Properties", "", "BLENDER", 9),
]
+8 -22
View File
@@ -202,7 +202,9 @@ 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(
@@ -341,7 +343,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(context.scene.DocProperties, "shadingstyle_default")
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawing_font")
row.prop(context.scene.DocProperties, "magic_font_scale")
# Scene panel groups
@@ -362,7 +363,6 @@ class BIM_PT_tabs(Panel):
aprops = context.screen.BIMTabProperties
row = self.layout.row()
row.alignment = "CENTER"
row.operator(
"bim.set_tab",
text="",
@@ -378,11 +378,11 @@ class BIM_PT_tabs(Panel):
self.draw_tab_entry(row, "NLA", "SCHEDULING", is_ifc_project, aprops.tab == "SCHEDULING")
self.draw_tab_entry(row, "PACKAGE", "FM", True, aprops.tab == "FM")
self.draw_tab_entry(row, "COMMUNITY", "QUALITY", True, aprops.tab == "QUALITY")
self.draw_tab_entry(row, "BLENDER", "BLENDER", True, aprops.tab == "BLENDER")
row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT")
# Yes, that's right.
row = self.layout.row()
row.alignment = "CENTER"
row.scale_y = 0.2
for tab in [
"PROJECT",
@@ -394,6 +394,7 @@ class BIM_PT_tabs(Panel):
"SCHEDULING",
"FM",
"QUALITY",
"BLENDER",
"SWITCH",
]:
if aprops.tab == tab:
@@ -406,7 +407,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")
@@ -441,20 +442,6 @@ class BIM_PT_tab_project_info(Panel):
pass
class BIM_PT_tab_spatial_decomposition(Panel):
bl_label = "Spatial Decomposition"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "PROJECT") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_project_setup(Panel):
bl_label = "Project Setup"
bl_space_type = "PROPERTIES"
@@ -503,7 +490,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):
@@ -516,9 +503,8 @@ 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):
+2 -13
View File
@@ -41,12 +41,9 @@ def assign_object(
related_obj: Optional[bpy.types.Object] = None,
) -> Union[ifcopenshell.entity_instance, None]:
if not aggregator.can_aggregate(relating_obj, related_obj):
raise IncompatibleAggregateError
relating_object = ifc.get_entity(relating_obj)
if aggregator.has_physical_body_representation(relating_object):
raise AggregateRepresentationError
return
rel = ifc.run(
"aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=relating_object
"aggregate.assign_object", products=[ifc.get_entity(related_obj)], relating_object=ifc.get_entity(relating_obj)
)
collector.assign(relating_obj)
collector.assign(related_obj)
@@ -87,11 +84,3 @@ 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,4 +484,12 @@ 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
+6 -6
View File
@@ -120,28 +120,28 @@ def select_product(spatial, product):
spatial.select_products([product])
def import_spatial_decomposition(spatial):
spatial.import_spatial_decomposition()
def load_container_manager(spatial):
spatial.load_container_manager()
def edit_container_attributes(spatial, entity=None):
spatial.edit_container_attributes(entity)
spatial.import_spatial_decomposition()
spatial.load_container_manager()
def contract_container(spatial, container=None):
spatial.contract_container(container)
spatial.import_spatial_decomposition()
spatial.load_container_manager()
def expand_container(spatial, container=None):
spatial.expand_container(container)
spatial.import_spatial_decomposition()
spatial.load_container_manager()
def delete_container(ifc, spatial, geometry, container=None):
geometry.delete_ifc_object(ifc.get_object(container))
spatial.import_spatial_decomposition()
spatial.load_container_manager()
def select_decomposed_elements(spatial):
+2 -2
View File
@@ -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 import_spatial_structure(cls, element, level_index): pass
def create_new_storey_li(cls, element, level_index): pass
def deselect_objects(cls): pass
def disable_editing(cls, obj): pass
def duplicate_object_and_data(cls, obj): pass
@@ -848,7 +848,7 @@ class Spatial:
def get_selected_product_types(cls): pass
def get_selected_products(cls): pass
def import_containers(cls, parent=None): pass
def import_spatial_decomposition(cls): pass
def load_container_manager(cls): pass
def run_root_copy_class(cls, obj=None): pass
def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): pass
def select_object(cls, obj): pass
+3 -10
View File
@@ -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_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a("IfcElement"):
if relating_obj.data: # See #3973
return False
return True
if tool.Ifc.get_schema() == "IFC2X3":
if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"):
@@ -46,13 +46,6 @@ 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
+3 -6
View File
@@ -95,12 +95,9 @@ 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
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
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
)
@classmethod
def assign(cls, obj: bpy.types.Object) -> None:
@@ -1752,8 +1752,6 @@ 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)
@@ -1841,10 +1839,6 @@ 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]
+8 -17
View File
@@ -725,11 +725,11 @@ class Geometry(blenderbim.core.tool.Geometry):
return False
@classmethod
def should_use_presentation_style_assignment(cls) -> bool:
def should_use_presentation_style_assignment(cls):
return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment
@classmethod
def get_model_representations(cls) -> list[ifcopenshell.entity_instance]:
def get_model_representations(cls):
return tool.Ifc.get().by_type("IfcShapeRepresentation")
@classmethod
@@ -761,25 +761,16 @@ 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 = {(obj:=tool.Ifc.get_object(element)).data: obj for element in elements}
meshes_to_objects = dict()
for obj in objs:
mesh = obj.data
if not mesh:
continue
meshes_to_objects.setdefault(mesh, obj)
for obj in meshes_to_objects.values():
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
+30 -88
View File
@@ -207,73 +207,45 @@ class Spatial(blenderbim.core.tool.Spatial):
src_obj.location = (destination_obj.location[0], destination_obj.location[1], z)
@classmethod
def load_contained_elements(cls):
props = bpy.context.scene.BIMSpatialDecompositionProperties
props.elements.clear()
if not (container := props.active_container):
return
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]
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
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)
@classmethod
def import_spatial_decomposition(cls):
props = bpy.context.scene.BIMSpatialDecompositionProperties
previous_container_index = props.active_container_index
props.containers.clear()
cls.contracted_containers = json.loads(props.contracted_containers)
cls.import_spatial_element(tool.Ifc.get().by_type("IfcProject")[0], 0)
props.active_container_index = min(previous_container_index, len(props.containers) - 1)
@classmethod
def import_spatial_element(cls, element, level_index):
props = bpy.context.scene.BIMSpatialDecompositionProperties
new = props.containers.add()
new.ifc_class = element.is_a()
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.description = element.Description or ""
new.long_name = element.LongName or ""
if not element.is_a("IfcProject"):
new.elevation = ifcopenshell.util.placement.get_storey_elevation(element)
new.has_decomposition = bool(element.IsDecomposedBy)
new.ifc_definition_id = element.id()
new.elevation = ifcopenshell.util.placement.get_storey_elevation(element) * si_conversion
new.is_expanded = element.id() not in cls.contracted_containers
new.level_index = level_index
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_element(child, level_index + 1)
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)
@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.BIMSpatialDecompositionProperties.container_name
name = bpy.context.scene.BIMSpatialManagerProperties.container_name
if name != entity.Name:
cls.edit_container_name(entity, name)
@@ -283,21 +255,21 @@ class Spatial(blenderbim.core.tool.Spatial):
@classmethod
def get_active_container(cls):
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = bpy.context.scene.BIMSpatialManagerProperties
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.BIMSpatialDecompositionProperties
props = bpy.context.scene.BIMSpatialManagerProperties
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.BIMSpatialDecompositionProperties
props = bpy.context.scene.BIMSpatialManagerProperties
contracted_containers = json.loads(props.contracted_containers)
contracted_containers.remove(container.id())
props.contracted_containers = json.dumps(contracted_containers)
@@ -322,36 +294,6 @@ class Spatial(blenderbim.core.tool.Spatial):
space_polygon = shapely.force_3d(polygon)
return space_polygon
@classmethod
def debug_shape(cls, foo):
coords = [(p[0], p[1], 0) for p in foo.exterior.coords]
mesh = bpy.data.meshes.new(name="NewMesh")
bm = bmesh.new()
for coord in coords:
bm.verts.new(coord)
bm.verts.ensure_lookup_table()
bm.faces.new(bm.verts)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("NewObject", mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.update()
@classmethod
def debug_line(cls, start, end):
coords = [start, end]
mesh = bpy.data.meshes.new(name="NewMesh")
bm = bmesh.new()
for coord in coords:
bm.verts.new(coord)
bm.verts.ensure_lookup_table()
bm.edges.new(bm.verts)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("NewLine", mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.update()
@classmethod
def get_boundary_lines_from_context_visible_objects(cls):
calculation_rl = bpy.context.scene.BIMModelProperties.rl3
+1 -1
View File
@@ -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/ladybug-tools/ladybug-blender/releases/download/ladybug-blender-240529/ladybug-blender-240529.zip>`__ - Ladybug Tools
<https://github.com/Andrej730/ladybug-blender/releases/download/ladybug-blender-240419/ladybug-blender-240419.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
+8 -19
View File
@@ -18,9 +18,6 @@
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
@@ -95,24 +92,16 @@ class TestCanAggregate(NewFile):
subelement_obj = bpy.data.objects.new("Object", None)
assert subject.can_aggregate(element_obj, subelement_obj) is False
class TestHasPhysicalBodyRepresentation(NewFile):
def test_run(self):
def test_aggregates_with_meshes_are_invalid(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcElementAssembly()
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
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
class TestDisableEditing(NewFile):
+119 -30
View File
@@ -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,6 +45,48 @@ 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
@@ -55,14 +97,12 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
def setup_file(self):
self.ifc = ifcopenshell.file()
tool.Ifc.set(self.ifc)
ifcopenshell.api.run("root.create_entity", self.ifc, ifc_class="IfcProject", name="My Project")
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
@@ -75,32 +115,12 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
predefined_type="ELEMENTEDWALL",
context=context,
)
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}
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
def test_meters_project_unit(self):
self.setup_file()
@@ -166,6 +186,35 @@ 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()
@@ -187,6 +236,46 @@ 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):
+1 -1
View File
@@ -76,7 +76,7 @@ class SI2ProjectUnitConverter:
"IfcLengthMeasure": "METRE",
"IfcMassMeasure": "GRAM",
"IfcTimeMeasure": "SECOND",
"IfcVolumeMeasure": "CUBIC_METRE",
"IfcVolumeMeasure": "CUBIE_METRE",
}
def convert(self, value, measure):
@@ -335,8 +335,8 @@ multiple times.
results = tree.select_ray(origin, direction, length=5.)
for result in results:
print(ifc_file.by_id(result.instance.id())) # The element the ray intersects with
print(list(result.position)) # The XYZ intersection point
print(result.distance) # The distance between the ray origin and the intersection
print(list(result.normal)) # The normal of the face being intersected
print(result.dot_product) # The dot product of the face being intersected with the ray
print(ifc_file.by_id(r.instance.id())) # The element the ray intersects with
print(list(r.position)) # The XYZ intersection point
print(r.distance) # The distance between the ray origin and the intersection
print(list(r.normal)) # The normal of the face being intersected
print(r.dot_product) # The dot product of the face being intersected with the ray
@@ -102,7 +102,6 @@ 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
@@ -184,7 +183,6 @@ 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,8 +28,3 @@ from .assign_object import assign_object
from .unassign_object import unassign_object
wrap_usecases(__path__, __name__)
__all__ = [
"assign_object",
"unassign_object",
]
@@ -27,7 +27,3 @@ from .. import wrap_usecases
from .edit_attributes import edit_attributes
wrap_usecases(__path__, __name__)
__all__ = [
"edit_attributes",
]
@@ -30,10 +30,3 @@ 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,12 +36,3 @@ 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,15 +34,3 @@ 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,9 +31,3 @@ from .edit_context import edit_context
from .remove_context import remove_context
wrap_usecases(__path__, __name__)
__all__ = [
"add_context",
"edit_context",
"remove_context",
]
@@ -27,8 +27,3 @@ from .assign_control import assign_control
from .unassign_control import unassign_control
wrap_usecases(__path__, __name__)
__all__ = [
"assign_control",
"unassign_control",
]
@@ -46,25 +46,3 @@ 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,14 +35,3 @@ 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,9 +28,3 @@ 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,28 +61,3 @@ 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,9 +29,3 @@ from .edit_georeferencing import edit_georeferencing
from .remove_georeferencing import remove_georeferencing
wrap_usecases(__path__, __name__)
__all__ = [
"add_georeferencing",
"edit_georeferencing",
"remove_georeferencing",
]
@@ -30,9 +30,3 @@ 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,12 +32,3 @@ 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,11 +33,3 @@ 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,14 +34,3 @@ 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,30 +58,3 @@ 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,10 +34,3 @@ 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,29 +50,3 @@ 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,11 +30,3 @@ 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,10 +33,3 @@ 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,11 +31,3 @@ 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,12 +33,3 @@ 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,18 +38,3 @@ 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,7 +17,6 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.element
import ifcopenshell.util.resource
def add_resource_quantity(
@@ -67,14 +66,6 @@ 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,9 +74,8 @@ def remove_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_insta
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
# Usage was added in IFC4.
if usage := getattr(settings["resource"], "Usage", None):
file.remove(usage)
if settings["resource"].Usage:
file.remove(settings["resource"].Usage)
if settings["resource"].BaseQuantity:
ifcopenshell.api.run(
"resource.remove_resource_quantity",
@@ -33,10 +33,3 @@ 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,44 +67,3 @@ 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,10 +29,3 @@ 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,27 +46,3 @@ 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,18 +38,3 @@ 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,18 +39,3 @@ 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,9 +30,3 @@ 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,16 +36,3 @@ 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,10 +31,3 @@ 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 TypeError(
raise ValueError(
"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,8 +648,6 @@ 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]:
@@ -1043,9 +1041,7 @@ 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:
@@ -1069,9 +1065,7 @@ 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:
@@ -1090,9 +1084,7 @@ 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:
@@ -1110,9 +1102,7 @@ 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:
@@ -1126,14 +1116,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, None]:
def get_nest(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance]:
"""
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: Union[ifcopenshell.entity_instance, None]
:rtype: ifcopenshell.entity_instance
Example:
@@ -1169,28 +1159,6 @@ 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]:
@@ -1220,7 +1188,6 @@ 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,7 +17,6 @@
# 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
@@ -99,12 +98,12 @@ def resolve_representation(representation: ifcopenshell.entity_instance) -> ifco
class ResolvedItemDict(TypedDict):
matrix: npt.NDArray[np.float64]
matrix: np.array
item: ifcopenshell.entity_instance
def resolve_items(
representation: ifcopenshell.entity_instance, matrix: Optional[npt.NDArray[np.float64]] = None
representation: ifcopenshell.entity_instance, matrix: Optional[np.array] = None
) -> list[ResolvedItemDict]:
if matrix is None:
matrix = np.eye(4)
@@ -23,20 +23,6 @@ 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,45 +687,21 @@ class FacetTransformer(lark.Transformer):
def parent(self, args):
comparison, value = args
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)
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
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
self.elements = set(filter(filter_function, self.elements))
def query(self, args):
keys, comparison, value = args
@@ -16,45 +16,27 @@
# 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()]
resource_types = [t.name() for t in schema.declaration_by_name("IfcConstructionResource").subtypes()]
self.file.create_entity("IfcProject") # add_resource
resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcCrewResource")
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)
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
class TestAddResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestAddResourceQuantity):
@@ -1,19 +0,0 @@
# 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.
+1 -1
View File
@@ -28,7 +28,7 @@ from typing import Union
def deprecation_check(test):
def new_test(self):
assert datetime.now().date() < datetime(2024, 8, 1).date(), "API arguments are completely deprecated"
assert datetime.now().date() < datetime(2024, 6, 1).date(), "API arguments are completely deprecated"
test(self)
return new_test
+1 -10
View File
@@ -16,22 +16,13 @@
# 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")