Merge pull request #2315 from cvillagrasa/v0.7.0

Addition of Construction Type Browser
This commit is contained in:
Dion Moult
2022-08-01 18:42:09 +10:00
committed by GitHub
17 changed files with 785 additions and 119 deletions
+11
View File
@@ -118,6 +118,17 @@ def prop_with_search(layout, data, prop_name, **kwargs):
op.prop_name = prop_name op.prop_name = prop_name
def layout_with_margins(layout, margin_left=0.025, margin_right=None):
margin_right = margin_left if margin_right is None else margin_right
split = layout.split(factor=margin_left, align=True)
cols = [split.column() for _ in range(2)]
cols[0].label(text="")
subsplit = cols[-1].split(factor=(1. - margin_right), align=True)
subcol = subsplit.column()
subsplit.column().label(text="")
return subcol
def get_enum_items(data, prop_name, context): def get_enum_items(data, prop_name, context):
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported # Retrieve items from a dynamic EnumProperty, which is otherwise not supported
# Or throws an error in the console when the items callback returns an empty list # Or throws an error in the console when the items callback returns an empty list
@@ -17,11 +17,14 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace, profile
classes = ( classes = (
product.AddEmptyType, product.AddEmptyType,
product.AddTypeInstance, product.AddConstrTypeInstance,
product.DisplayConstrTypes,
product.SelectConstructionType,
product.HelpConstrTypes,
product.AlignProduct, product.AlignProduct,
product.DynamicallyVoidProduct, product.DynamicallyVoidProduct,
workspace.Hotkey, workspace.Hotkey,
@@ -32,8 +35,8 @@ classes = (
opening.AddElementOpening, opening.AddElementOpening,
profile.ExtendProfile, profile.ExtendProfile,
prop.BIMModelProperties, prop.BIMModelProperties,
prop.ConstrTypeInfo,
ui.BIM_PT_authoring, ui.BIM_PT_authoring,
ui.BIM_PT_authoring_architectural,
grid.BIM_OT_add_object, grid.BIM_OT_add_object,
stair.BIM_OT_add_object, stair.BIM_OT_add_object,
opening.BIM_OT_add_object, opening.BIM_OT_add_object,
@@ -51,6 +54,7 @@ def register():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True) bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties) bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.Scene.ConstrTypeInfo = bpy.props.CollectionProperty(type=prop.ConstrTypeInfo)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
@@ -68,6 +72,7 @@ def unregister():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.unregister_tool(workspace.BimTool) bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties del bpy.types.Scene.BIMModelProperties
del bpy.types.Scene.ConstrTypeInfo
bpy.app.handlers.load_post.remove(handler.load_post) bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button) bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button) bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
@@ -16,8 +16,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import functools
import bpy import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
preview_icon_ids = {}
attempts = 0
def refresh(): def refresh():
@@ -31,10 +37,29 @@ class AuthoringData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.is_loaded = True cls.is_loaded = True
cls.data = { if not hasattr(cls, "data"):
"ifc_classes": cls.ifc_classes(), cls.data = {}
"relating_types": cls.relating_types(), cls.props = bpy.context.scene.BIMModelProperties
} cls.load_ifc_classes()
cls.load_relating_types()
cls.load_relating_types_browser()
cls.load_preview_constr_types()
@classmethod
def load_ifc_classes(cls):
cls.data["ifc_classes"] = cls.ifc_classes()
@classmethod
def load_relating_types(cls):
cls.data["relating_types_ids"] = cls.relating_types()
@classmethod
def load_relating_types_browser(cls):
cls.data["relating_types_ids_browser"] = cls.relating_types_browser()
@classmethod
def load_preview_constr_types(cls):
cls.data["preview_constr_types"] = preview_icon_ids
@classmethod @classmethod
def ifc_classes(cls): def ifc_classes(cls):
@@ -49,16 +74,131 @@ class AuthoringData:
return results return results
@classmethod @classmethod
def relating_types(cls): def constr_class_entities(cls, ifc_class=None):
ifc_classes = cls.ifc_classes() ifc_classes = cls.data["ifc_classes"]
if not ifc_classes: if not ifc_classes:
return [] return []
results = [] results = []
ifc_class = bpy.context.scene.BIMModelProperties.ifc_class if ifc_class is None:
ifc_class = cls.props.ifc_class
if not ifc_class and ifc_classes: if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0] ifc_class = ifc_classes[0][0]
if ifc_class: if ifc_class:
elements = [(str(e.id()), e.Name, e.Description or "") for e in tool.Ifc.get().by_type(ifc_class)] elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name)
results.extend(sorted(elements, key=lambda s: s[1])) results.extend(elements)
return results return results
return [] return []
@classmethod
def relating_types(cls, ifc_class=None):
return [
(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)
]
@classmethod
def relating_types_browser(cls):
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
@staticmethod
def new_relating_type_info(ifc_class):
relating_type_info = bpy.context.scene.ConstrTypeInfo.add()
relating_type_info.name = ifc_class
return relating_type_info
@classmethod
def assetize_constr_class(cls, ifc_class=None):
if ifc_class is None:
ifc_class = cls.props.ifc_class
relating_type_info = cls.relating_type_info(ifc_class)
_ = cls.new_relating_type_info(ifc_class) if relating_type_info is None else relating_type_info
constr_class_occurrences = cls.constr_class_entities(ifc_class)
preview_constr_types = cls.data["preview_constr_types"]
for constr_class_entity in constr_class_occurrences:
### handle asset regeneration when library entity is updated ¿?
if (ifc_class not in preview_constr_types
or str(constr_class_entity.id()) not in preview_constr_types[ifc_class]):
obj = tool.Ifc.get_object(constr_class_entity)
cls.assetize_object(obj, ifc_class, constr_class_entity)
relating_type_info = cls.relating_type_info(ifc_class)
relating_type_info.fully_loaded = True
@classmethod
def assetize_object(cls, obj, ifc_class, ifc_class_entity, from_selection=False):
relating_type_id = ifc_class_entity.id()
to_be_deleted = False
if obj.type == 'EMPTY':
kwargs = {}
if not from_selection:
kwargs.update({'ifc_class': ifc_class, 'relating_type_id': relating_type_id})
new_obj = cls.new_relating_type(**kwargs)
if new_obj is not None:
to_be_deleted = True
obj = new_obj
obj.asset_mark()
obj.asset_generate_preview()
icon_id = obj.preview.icon_id
if ifc_class not in cls.data["preview_constr_types"]:
cls.data["preview_constr_types"][ifc_class] = {}
cls.data["preview_constr_types"][ifc_class][str(relating_type_id)] = {"icon_id": icon_id, "object": obj}
if to_be_deleted:
for col in obj.users_collection:
col.objects.unlink(obj)
@classmethod
def assetize_relating_type_from_selection(cls):
ifc_class_browser = cls.props.ifc_class_browser
relating_type_id_browser = cls.props.relating_type_id_browser
constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class_browser)
constr_class_occurrences = [
entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id_browser)
]
if len(constr_class_occurrences) == 0:
return False
constr_class_entity = constr_class_occurrences[0]
obj = tool.Ifc.get_object(constr_class_entity)
if obj is None:
return False
cls.assetize_object(obj, ifc_class_browser, constr_class_entity, from_selection=True)
return True
@staticmethod
def relating_type_info(ifc_class):
relating_type_infos = [element for element in bpy.context.scene.ConstrTypeInfo if element.name == ifc_class]
return None if len(relating_type_infos) == 0 else relating_type_infos[0]
@classmethod
def new_relating_type(cls, ifc_class=None, relating_type_id=None):
if ifc_class is None:
bpy.ops.bim.add_constr_type_instance(
ifc_class=cls.props.ifc_class_browser, relating_type_id=int(cls.props.relating_type_id_browser)
)
else:
cls.props.ifc_class = ifc_class
cls.props.relating_type_id = str(relating_type_id)
bpy.ops.bim.add_constr_type_instance()
return bpy.context.selected_objects[-1]
@staticmethod
def relating_type_name_by_id(ifc_class, relating_type_id):
file = IfcStore.get_file()
try:
constr_class_entity = file.by_id(int(relating_type_id))
except (RuntimeError, ValueError):
return None
return constr_class_entity.Name if constr_class_entity.is_a() == ifc_class else None
@classmethod
def relating_type_id_by_name(cls, ifc_class, relating_type):
relating_types = [ct[0] for ct in cls.relating_types(ifc_class=ifc_class) if ct[1] == relating_type]
return None if len(relating_types) == 0 else relating_types[0]
@classmethod
def consolidate_relating_type(cls):
cls.props.ifc_class = cls.props.ifc_class_browser
cls.props.relating_type_id = cls.props.relating_type_id_browser
@classmethod
def setup_relating_type_browser(cls):
cls.props.ifc_class_browser = cls.props.ifc_class
cls.props.relating_type_id_browser = cls.props.relating_type_id
@@ -114,5 +114,13 @@ class MepGenerator:
tool.Ifc.run("system.assign_port", element=element, port=port) tool.Ifc.run("system.assign_port", element=element, port=port)
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True) tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
obj.select_set(True) try:
obj.select_set(True)
except RuntimeError:
def msg(self, context):
txt = "The created object could not be assigned to a collection. "
txt += "Has any IfcSpatialElement been deleted?"
self.layout.label(text=txt)
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return obj return obj
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import math
import mathutils import mathutils
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
@@ -29,9 +30,12 @@ import blenderbim.core.type
import blenderbim.core.geometry import blenderbim.core.geometry
from . import wall, slab, profile, mep from . import wall, slab, profile, mep
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.helper import prop_with_search, layout_with_margins, close_operator_panel
from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bpy_extras.object_utils import AddObjectHelper from bpy_extras.object_utils import AddObjectHelper
from . import prop
class AddEmptyType(bpy.types.Operator, AddObjectHelper): class AddEmptyType(bpy.types.Operator, AddObjectHelper):
@@ -53,13 +57,19 @@ def add_empty_type_button(self, context):
self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D") self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
class AddTypeInstance(bpy.types.Operator): class AddConstrTypeInstance(bpy.types.Operator):
bl_idname = "bim.add_type_instance" bl_idname = "bim.add_constr_type_instance"
bl_label = "Add Type Instance" bl_label = "Add"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Add the selected Type Instance to the model" bl_description = "Add Type Instance to the model"
ifc_class: bpy.props.StringProperty() ifc_class: bpy.props.StringProperty()
relating_type: bpy.props.IntProperty() relating_type_id: bpy.props.IntProperty()
from_invoke: bpy.props.BoolProperty(default=False)
def invoke(self, context, event):
if self.from_invoke:
close_operator_panel(event)
return self.execute(context)
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
@@ -67,11 +77,15 @@ class AddTypeInstance(bpy.types.Operator):
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
ifc_class = self.ifc_class or props.ifc_class ifc_class = self.ifc_class or props.ifc_class
relating_type_id = self.relating_type or props.relating_type relating_type_id = self.relating_type_id or props.relating_type_id
if not ifc_class or not relating_type_id: if not ifc_class or not relating_type_id:
return {"FINISHED"} return {"FINISHED"}
if self.from_invoke:
props.ifc_class = self.ifc_class
props.relating_type_id = str(self.relating_type_id)
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0] instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
relating_type = self.file.by_id(int(relating_type_id)) relating_type = self.file.by_id(int(relating_type_id))
@@ -150,7 +164,8 @@ class AddTypeInstance(bpy.types.Operator):
context.view_layer.objects.active = obj context.view_layer.objects.active = obj
return {"FINISHED"} return {"FINISHED"}
def generate_layered_element(self, ifc_class, relating_type): @staticmethod
def generate_layered_element(ifc_class, relating_type):
layer_set_direction = None layer_set_direction = None
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
@@ -172,6 +187,217 @@ class AddTypeInstance(bpy.types.Operator):
pass # Dumb block generator? Eh? :) pass # Dumb block generator? Eh? :)
class DisplayConstrTypes(bpy.types.Operator):
bl_idname = "bim.display_relating_types"
bl_label = "Browse Construction Types"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Display all available Construction Types to add new instances"
def execute(self, context):
return {"FINISHED"}
def invoke(self, context, event):
if not AuthoringData.is_loaded:
AuthoringData.load()
AuthoringData.setup_relating_type_browser()
props = context.scene.BIMModelProperties
if props.unfold_relating_types:
ifc_class = props.ifc_class_browser
relating_type_info = AuthoringData.relating_type_info(ifc_class)
if relating_type_info is None or not relating_type_info.fully_loaded:
AuthoringData.assetize_constr_class(ifc_class)
else:
prop.update_relating_type_browser(props, context)
min_width = 250
width_scaling = 5 ** -1
width = max([min_width, int(width_scaling * context.region.width)])
return context.window_manager.invoke_popup(self, width=width)
def draw(self, context):
props = context.scene.BIMModelProperties
header_data = self.draw_header(props)
if props.unfold_relating_types:
self.draw_by_ifc_class(props, header_data)
else:
self.draw_by_ifc_class_and_type(props, header_data)
def draw_header(self, props):
layout = self.layout
inner_layout = layout_with_margins(layout, margin_left=0.004)
inner_layout.row().separator(factor=0.75)
split = inner_layout.split(align=True, factor=2./3)
col1 = split.column(align=True)
row = col1.row()
row.prop(data=props, property="unfold_relating_types", text="Preview All Construction Types")
col1.row().separator(factor=1)
row = col1.row()
row.label(text="Select Construction Type:")
col1.row().separator(factor=1.5)
enabled = True
if AuthoringData.data["ifc_classes"]:
subsplit = col1.split(factor=1./3)
subsplit.column().row().label(text="Construction Class:", icon="FILE_VOLUME")
prop_with_search(subsplit.column(), props, "ifc_class_browser", text="")
col1.row().separator()
else:
enabled = False
col2 = split.column(align=True)
subsplit = col2.split(factor=0.9)
subcol = [subsplit.column() for _ in range(2)][-1]
subcol.operator("bim.help_relating_types", text="", icon="QUESTION")
col2.row().separator(factor=1)
return {"enabled": enabled, "layout": inner_layout, "col1": col1, "col2": col2}
def draw_by_ifc_class(self, props, header_data):
enabled, layout = [header_data[key] for key in ["enabled", "layout"]]
ifc_class_browser = props.ifc_class_browser
num_cols = 3
layout.row().separator(factor=0.25)
layout.row().label(text="Construction Types:", icon="FILE_3D")
layout.row().separator(factor=0.25)
flow = layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True)
relating_types_browser = AuthoringData.relating_types_browser()
num_types = len(relating_types_browser)
for idx, (relating_type_id_browser, name, desc) in enumerate(relating_types_browser):
outer_col = flow.column()
box = outer_col.box()
row = box.row()
row.label(text=name, icon="FILE_3D")
row.alignment = "CENTER"
row = box.row()
if enabled:
preview_constr_types = AuthoringData.data["preview_constr_types"]
if ifc_class_browser in preview_constr_types:
preview_ifc_class = preview_constr_types[ifc_class_browser]
if relating_type_id_browser in preview_ifc_class:
icon_id = preview_ifc_class[relating_type_id_browser]["icon_id"]
row.template_icon(icon_value=icon_id, scale=6.)
box.row().separator(factor=0.2)
row = box.row()
split = row.split(factor=0.5)
col = split.column()
op = col.operator("bim.select_construction_type", icon="RIGHTARROW_THIN")
op.ifc_class = ifc_class_browser
op.relating_type_id = relating_type_id_browser
col = split.column()
op = col.operator("bim.add_constr_type_instance", icon="ADD")
op.from_invoke = True
op.ifc_class = ifc_class_browser
if relating_type_id_browser.isnumeric():
op.relating_type_id = int(relating_type_id_browser)
factor = 2 if idx + 1 < math.ceil(num_types / num_cols) else 1.5
outer_col.row().separator(factor=factor)
last_row_cols = num_types % num_cols
if last_row_cols != 0:
for _ in range(num_cols - last_row_cols):
flow.column()
def draw_by_ifc_class_and_type(self, props, header_data):
enabled, col1, col2 = [header_data[key] for key in ["enabled", "col1", "col2"]]
ifc_class_browser = props.ifc_class_browser
relating_type_id_browser = props.relating_type_id_browser
if AuthoringData.data["relating_types_ids_browser"]:
subsplit = col1.split(factor=1. / 3)
subsplit.column().row().label(text="Construction Type:", icon="FILE_3D")
prop_with_search(subsplit.column(), props, "relating_type_id_browser", text="")
col1.row().separator()
else:
enabled = False
col1.row().separator(factor=4.75)
row = col1.row()
row.enabled = enabled
op = row.operator("bim.select_construction_type", icon="RIGHTARROW_THIN")
op.ifc_class = ifc_class_browser
op.relating_type_id = relating_type_id_browser
op = row.operator("bim.add_constr_type_instance", icon="ADD")
op.from_invoke = True
op.ifc_class = ifc_class_browser
if relating_type_id_browser.isnumeric():
op.relating_type_id = int(relating_type_id_browser)
col2.row().separator(factor=1.25)
split = col2.split(factor=0.025)
col = [split.column() for _ in range(2)][-1]
box = col.box()
if enabled:
box.template_icon(icon_value=props.icon_id, scale=5.6)
col1.row().separator(factor=1)
class SelectConstructionType(bpy.types.Operator):
bl_idname = "bim.select_construction_type"
bl_label = "Select"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Pick Type Instance as selection for subsequent operations"
ifc_class: bpy.props.StringProperty()
relating_type_id: bpy.props.StringProperty()
def invoke(self, context, event):
close_operator_panel(event)
return self.execute(context)
def execute(self, context):
props = context.scene.BIMModelProperties
if self.ifc_class != "":
props.ifc_class = self.ifc_class
AuthoringData.load_relating_types()
if self.relating_type_id != "":
props.relating_type_id = self.relating_type_id
return {"FINISHED"}
class HelpConstrTypes(bpy.types.Operator):
bl_idname = "bim.help_relating_types"
bl_label = "Construction Types Help"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Click to read some contextual help"
def execute(self, context):
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=510)
def draw(self, context):
layout = self.layout
layout.row().separator(factor=0.5)
row = layout.row()
row.alignment = "CENTER"
row.label(text="BlenderBIM Help", icon="BLENDER")
layout.row().separator(factor=0.5)
row = layout_with_margins(layout.row()).row()
row.label(text="Overview:", icon="KEYTYPE_MOVING_HOLD_VEC")
self.draw_lines(layout, self.message_summary)
layout.row().separator()
row = layout_with_margins(layout.row()).row()
row.label(text="Further support:", icon="KEYTYPE_MOVING_HOLD_VEC")
layout.row().separator(factor=0.5)
row = layout_with_margins(layout).row()
op = row.operator("bim.open_upstream", text="Homepage", icon="HOME")
op.page = "home"
op = row.operator("bim.open_upstream", text="Docs", icon="DOCUMENTS")
op.page = "docs"
op = row.operator("bim.open_upstream", text="Wiki", icon="CURRENT_FILE")
op.page = "wiki"
op = row.operator("bim.open_upstream", text="Community", icon="COMMUNITY")
op.page = "community"
layout.row().separator()
def draw_lines(self, layout, lines):
box = layout_with_margins(layout).box()
for line in lines:
row = box.row()
row.label(text=f" {line}")
@property
def message_summary(self):
return [
'The Construction Type Browser allows to preview and add new instances to the model.',
'For further support, please click on the Documentation link below.'
]
class AlignProduct(bpy.types.Operator): class AlignProduct(bpy.types.Operator):
bl_idname = "bim.align_product" bl_idname = "bim.align_product"
bl_label = "Align Product" bl_label = "Align Product"
@@ -162,7 +162,15 @@ class DumbProfileGenerator:
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"}) ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
MaterialData.load(self.file) MaterialData.load(self.file)
obj.select_set(True) try:
obj.select_set(True)
except RuntimeError:
def msg(self, context):
txt = "The created object could not be assigned to a collection. "
txt += "Has any IfcSpatialElement been deleted?"
self.layout.label(text=txt)
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return obj return obj
@@ -17,21 +17,8 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import ifcopenshell.util.type
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def get_ifc_class(self, context): def get_ifc_class(self, context):
@@ -43,18 +30,110 @@ def get_ifc_class(self, context):
def get_relating_type(self, context): def get_relating_type(self, context):
if not AuthoringData.is_loaded: if not AuthoringData.is_loaded:
AuthoringData.load() AuthoringData.load()
return AuthoringData.data["relating_types"] return AuthoringData.data["relating_types_ids"]
def get_relating_type_browser(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["relating_types_ids_browser"]
def update_icon_id(self, context):
ifc_class_browser = self.ifc_class_browser
relating_type_id_browser = self.relating_type_id_browser
relating_type_browser = AuthoringData.relating_type_name_by_id(ifc_class_browser, relating_type_id_browser)
if ((ifc_class_browser not in AuthoringData.data["preview_constr_types"]
or relating_type_id_browser not in AuthoringData.data["preview_constr_types"][ifc_class_browser])
and relating_type_browser is not None):
if not AuthoringData.assetize_relating_type_from_selection():
return
self.icon_id = AuthoringData.data["preview_constr_types"][ifc_class_browser][relating_type_id_browser]["icon_id"]
def update_ifc_class(self, context): def update_ifc_class(self, context):
AuthoringData.is_loaded = False AuthoringData.load_ifc_classes()
AuthoringData.load_relating_types()
self.relating_type_id = AuthoringData.data["relating_types_ids"][0][0]
def update_ifc_class_browser(self, context):
AuthoringData.load_ifc_classes()
AuthoringData.load_relating_types_browser()
props = context.scene.BIMModelProperties
if props.unfold_relating_types:
ifc_class_browser = props.ifc_class_browser
relating_type_info = AuthoringData.relating_type_info(ifc_class_browser)
if relating_type_info is None or not relating_type_info.fully_loaded:
curr_selection = props.ifc_class, props.relating_type_id
AuthoringData.assetize_constr_class(ifc_class_browser)
props.ifc_class, props.relating_type_id = curr_selection
else:
self.relating_type_id_browser = AuthoringData.data["relating_types_ids_browser"][0][0]
def update_relating_type(self, context):
AuthoringData.load_relating_types()
def update_relating_type_by_name(self, context):
AuthoringData.load_relating_types()
relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type)
if relating_type_id is not None:
self.relating_type_id = relating_type_id
def update_relating_type_browser_by_name(self, context):
AuthoringData.load_relating_types_browser()
relating_type_id_browser = AuthoringData.relating_type_id_by_name(self.ifc_class_browser, self.relating_type_browser)
if relating_type_id_browser is not None:
self.relating_type_id_browser = relating_type_id_browser
def update_relating_type_browser(self, context):
AuthoringData.load_relating_types_browser()
update_icon_id(self, context)
def update_unfold_relating_type(self, context):
update_ifc_class_browser(self, context)
class BIMModelProperties(PropertyGroup): class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class) ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type") ifc_class_browser: bpy.props.EnumProperty(
items=get_ifc_class, name="Construction Class", update=update_ifc_class_browser
)
relating_type: bpy.props.StringProperty(update=update_relating_type_by_name)
relating_type_id: bpy.props.EnumProperty(
items=get_relating_type, name="Construction Type", update=update_relating_type
)
relating_type_browser: bpy.props.StringProperty(update=update_relating_type_browser_by_name)
relating_type_id_browser: bpy.props.EnumProperty(
items=get_relating_type_browser, name="Construction Type", update=update_relating_type_browser
)
icon_id: bpy.props.IntProperty()
unfold_relating_types: bpy.props.BoolProperty(update=update_unfold_relating_type)
occurrence_name_style: bpy.props.EnumProperty( occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")], items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style", name="Occurrence Name Style",
) )
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function") occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
getter_enum = {
"ifc_class_browser": get_ifc_class,
"relating_type_browser": get_relating_type_browser
}
def get_relating_type_info(self, context):
return AuthoringData.relating_types(ifc_class=self.name)
class ConstrTypeInfo(PropertyGroup):
name: bpy.props.StringProperty(name="Construction class")
relating_type: bpy.props.EnumProperty(
name="Construction type", items=get_relating_type_info
)
fully_loaded: bpy.props.BoolProperty(default=False)
@@ -166,7 +166,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
obj = settings["blender_object"] obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id) product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric") parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3": if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return return
qto = ifcopenshell.api.run( qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities" "pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
@@ -306,7 +306,15 @@ class DumbSlabGenerator:
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"}) ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"})
MaterialData.load(self.file) MaterialData.load(self.file)
obj.select_set(True) try:
obj.select_set(True)
except RuntimeError:
def msg(self, context):
txt = "The created object could not be assigned to a collection. "
txt += "Has any IfcSpatialElement been deleted?"
self.layout.label(text=txt)
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return obj return obj
@@ -342,6 +350,23 @@ class DumbSlabPlaner:
return return
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
material = ifcopenshell.util.element.get_material(settings["related_object"]) material = ifcopenshell.util.element.get_material(settings["related_object"])
relating_type = settings["relating_type"]
if hasattr(relating_type, "HasPropertySets"):
psets = relating_type.HasPropertySets
if psets is not None:
for pset in psets:
if hasattr(pset, "HasProperties"):
pset_props = pset.HasProperties
if pset_props is not None:
for prop in pset_props:
if prop.Name == "LayerSetDirection":
if hasattr(prop, "NominalValue"):
nominal_value = prop.NominalValue
if hasattr(nominal_value, "wrappedValue"):
if nominal_value.wrappedValue == "AXIS2":
return
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3": if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
self.change_thickness(settings["related_object"], new_thickness) self.change_thickness(settings["related_object"], new_thickness)
@@ -16,54 +16,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.module.type.prop as type_prop
from bpy.types import Panel from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
class BIM_PT_authoring(Panel): class BIM_PT_authoring(Panel):
bl_idname = "BIM_PT_authoring"
bl_label = "Authoring"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
props = context.scene.BIMModelProperties
col = self.layout.column(align=True)
enabled = True
if AuthoringData.data["ifc_classes"]:
col.prop(props, "ifc_class", text="", icon="FILE_VOLUME")
else:
col.label(text="No IFC Class", icon="FILE_VOLUME")
enabled = False
if AuthoringData.data["relating_types"]:
col.prop(props, "relating_type", text="", icon="FILE_3D")
else:
col.label(text="No Relating Type", icon="FILE_3D")
enabled = False
row = col.row()
row.operator("bim.add_type_instance", icon="ADD")
row.enabled = enabled
class BIM_PT_authoring_architectural(Panel):
bl_label = "Architectural" bl_label = "Architectural"
bl_idname = "BIM_PT_authoring_architectural" bl_idname = "BIM_PT_authoring"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
bl_category = "BlenderBIM" bl_category = "BlenderBIM"
bl_parent_id = "BIM_PT_authoring"
def draw(self, context): def draw(self, context):
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -817,7 +817,15 @@ class DumbWallGenerator:
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"}) ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
MaterialData.load(self.file) MaterialData.load(self.file)
obj.select_set(True) try:
obj.select_set(True)
except RuntimeError:
def msg(self, context):
txt = "The created object could not be assigned to a collection. "
txt += "Has any IfcSpatialElement been deleted?"
self.layout.label(text=txt)
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return obj return obj
@@ -861,7 +869,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
obj = settings["blender_object"] obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id) product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric") parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2": if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return return
qto = ifcopenshell.api.run( qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities" "pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities"
@@ -941,6 +949,23 @@ class DumbWallPlaner:
return return
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
material = ifcopenshell.util.element.get_material(settings["related_object"]) material = ifcopenshell.util.element.get_material(settings["related_object"])
relating_type = settings["relating_type"]
if hasattr(relating_type, "HasPropertySets"):
psets = relating_type.HasPropertySets
if psets is not None:
for pset in psets:
if hasattr(pset, "HasProperties"):
pset_props = pset.HasProperties
if pset_props is not None:
for prop in pset_props:
if prop.Name == "LayerSetDirection":
if hasattr(prop, "NominalValue"):
nominal_value = prop.NominalValue
if hasattr(nominal_value, "wrappedValue"):
if nominal_value.wrappedValue == "AXIS3":
return
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2": if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2":
self.change_thickness(settings["related_object"], new_thickness) self.change_thickness(settings["related_object"], new_thickness)
@@ -55,28 +55,56 @@ class BimTool(WorkSpaceTool):
if not AuthoringData.is_loaded and IfcStore.get_file(): if not AuthoringData.is_loaded and IfcStore.get_file():
AuthoringData.load() AuthoringData.load()
props = context.scene.BIMModelProperties
is_tool_header = context.region.type == "TOOL_HEADER"
row = layout.row(align=True) row = layout.row(align=True)
if not IfcStore.get_file(): if not IfcStore.get_file():
row.label(text="No IFC Project", icon="ERROR") row.label(text="No IFC Project", icon="ERROR")
return return
props = context.scene.BIMModelProperties
if AuthoringData.data["ifc_classes"]:
row.prop(props, "ifc_class", text="")
else:
row.label(text="No IFC Class")
if AuthoringData.data["relating_types"]:
row.prop(props, "relating_type", text="")
else:
row.label(text="No Relating Type")
row.label(text="", icon="BLANK1") ifc_classes = AuthoringData.data["ifc_classes"]
relating_types_ids = AuthoringData.data["relating_types_ids"]
row = layout.row(align=True) if is_tool_header:
row.label(text="", icon="EVENT_SHIFT") row.operator("bim.help_relating_types", text="", icon="QUESTION")
row.label(text="Add Type Instance", icon="EVENT_A")
if ifc_classes and is_tool_header:
row.label(text="", icon="BLANK1")
row.operator("bim.display_relating_types", icon="COLLAPSEMENU")
ifc_class = props.ifc_class
relating_type_id = props.relating_type_id
relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
if is_tool_header:
row.label(text="", icon="BLANK1")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
if ifc_classes:
row.label(text=f" Add")
row.label(text="", icon="FILE_VOLUME")
row.label(text=ifc_class)
row.label(text="", icon="FILE_3D")
row.label(text=f"{relating_type} ")
else:
row.label(text=f" Add instance")
else:
txt_ifc_class = ifc_class if ifc_classes else "No Construction Class"
txt_relating_type = relating_type if relating_types_ids else "No Construction Type"
row = layout.row(align=True)
row.label(text="Selected Construction Type:")
row = layout.row(align=True)
row.label(text=txt_ifc_class, icon="FILE_VOLUME")
row = layout.row(align=True)
row.label(text=txt_relating_type, icon="FILE_3D")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
row.label(text=f" Add Type Instance")
if AuthoringData.data["ifc_classes"]: if AuthoringData.data["ifc_classes"]:
if props.ifc_class == "IfcWallType": if ifc_class == "IfcWallType":
row = layout.row() row = layout.row()
row.label(text="Join") row.label(text="Join")
row = layout.row(align=True) row = layout.row(align=True)
@@ -98,7 +126,7 @@ class BimTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="Split", icon="EVENT_S") row.label(text="Split", icon="EVENT_S")
if props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"): if ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
row = layout.row() row = layout.row()
row.label(text="Join") row.label(text="Join")
row = layout.row(align=True) row = layout.row(align=True)
@@ -156,7 +184,7 @@ class Hotkey(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def hotkey_S_A(self): def hotkey_S_A(self):
bpy.ops.bim.add_type_instance() bpy.ops.bim.add_constr_type_instance()
def hotkey_S_C(self): def hotkey_S_C(self):
if self.has_ifc_class and self.props.ifc_class == "IfcWallType": if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
@@ -88,4 +88,4 @@ class BIM_PT_type(Panel):
def add_object_button(self, context): def add_object_button(self, context):
self.layout.operator("bim.add_type_instance", icon="PLUGIN") self.layout.operator("bim.add_constr_type_instance", icon="PLUGIN")
+2 -1
View File
@@ -82,7 +82,8 @@ class Collector(blenderbim.core.tool.Collector):
if obj.users_collection != (object_collection,): if obj.users_collection != (object_collection,):
for collection in obj.users_collection: for collection in obj.users_collection:
collection.objects.unlink(obj) collection.objects.unlink(obj)
object_collection.objects.link(obj) if object_collection is not None:
object_collection.objects.link(obj)
if collection_collection and collection_collection.children.find(object_collection.name) == -1: if collection_collection and collection_collection.children.find(object_collection.name) == -1:
if bpy.context.scene.collection.children.find(object_collection.name) != -1: if bpy.context.scene.collection.children.find(object_collection.name) != -1:
@@ -31,8 +31,8 @@ Scenario: Add representation - add a new representation to a typed instance
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
When the object "IfcWall/Wall" is selected When the object "IfcWall/Wall" is selected
@@ -146,9 +146,9 @@ Scenario: Remove representation - remove an instanced representation from an act
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And the object "IfcWallType/Cube" is selected And the object "IfcWallType/Cube" is selected
When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()" When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
And I press "bim.remove_representation(representation_id={representation})" And I press "bim.remove_representation(representation_id={representation})"
@@ -165,9 +165,9 @@ Scenario: Remove representation - remove an instanced representation from an act
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And the object "IfcWall/Wall" is selected And the object "IfcWall/Wall" is selected
When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()" When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
And I press "bim.remove_representation(representation_id={representation})" And I press "bim.remove_representation(representation_id={representation})"
@@ -339,8 +339,8 @@ Scenario: Override duplicate move - copying a type instance with a representatio
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And the object "IfcWall/Wall" is selected And the object "IfcWall/Wall" is selected
When I press "object.duplicate_move" When I press "object.duplicate_move"
Then the object "IfcWall/Wall.001" exists Then the object "IfcWall/Wall.001" exists
+40 -7
View File
@@ -10,8 +10,8 @@ Scenario: Add type instance - add from a mesh
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
When I press "bim.add_type_instance" When I press "bim.add_constr_type_instance"
Then the object "IfcWall/Wall" exists Then the object "IfcWall/Wall" exists
Scenario: Add type instance - add from an empty Scenario: Add type instance - add from an empty
@@ -23,8 +23,8 @@ Scenario: Add type instance - add from an empty
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "empty" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "empty" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{empty}" And I set "scene.BIMModelProperties.relating_type_id" to "{empty}"
When I press "bim.add_type_instance" When I press "bim.add_constr_type_instance"
Then the object "IfcWall/Wall" exists Then the object "IfcWall/Wall" exists
Scenario: Add type instance - add a mesh where existing instances have changed context Scenario: Add type instance - add a mesh where existing instances have changed context
@@ -36,18 +36,51 @@ Scenario: Add type instance - add a mesh where existing instances have changed c
And I press "bim.assign_class" And I press "bim.assign_class"
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()" And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}" And I set "scene.BIMModelProperties.relating_type_id" to "{cube}"
And I press "bim.add_type_instance" And I press "bim.add_constr_type_instance"
And the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Wall" is selected And the object "IfcWall/Wall" is selected
And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.TargetView == 'PLAN_VIEW'][0].id()" And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.TargetView == 'PLAN_VIEW'][0].id()"
And I set "scene.BIMRootProperties.contexts" to "{context}" And I set "scene.BIMRootProperties.contexts" to "{context}"
And I press "bim.add_representation" And I press "bim.add_representation"
And the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" And the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
When I press "bim.add_type_instance" When I press "bim.add_constr_type_instance"
Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
And the object "IfcWall/Wall.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" And the object "IfcWall/Wall.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
Scenario: Preview one type on the Construction Type Browser
Given an empty IFC project
And I load the demo construction library
When I display the construction type browser
And I preview only one asset on the construction type browser
And I set "scene.BIMModelProperties.ifc_class_browser" to "IfcColumnType"
And I set "scene.BIMModelProperties.relating_type_browser" to "DEMO2"
And I select the browser construction type
Then "scene.BIMModelProperties.ifc_class" is "IfcColumnType"
And construction type is DEMO2
And objects starting with "IfcColumn/" do not exist
And the construction type "IfcColumnType"/"DEMO2" has a preview
Scenario: Preview one class on the construction type browser
Given an empty IFC project
And I load the demo construction library
When I display the construction type browser
And I preview all available assets on the construction type browser
And I set "scene.BIMModelProperties.ifc_class_browser" to "IfcWallType"
Then "scene.BIMModelProperties.ifc_class_browser" is "IfcWallType"
And objects starting with "IfcWall/" do not exist
And all construction types for "IfcWallType" have a preview
Scenario: Add one type from the Construction Type Browser
Given an empty IFC project
And I load the demo construction library
When I display the construction type browser
And I preview only one asset on the construction type browser
And I set "scene.BIMModelProperties.ifc_class_browser" to "IfcColumnType"
And I set "scene.BIMModelProperties.relating_type_browser" to "DEMO2"
And I add the browser construction type
Then the object "IfcColumn/Column" exists
Scenario: Add grid Scenario: Add grid
Given an empty IFC project Given an empty IFC project
When I press "mesh.add_grid" When I press "mesh.add_grid"
+117 -1
View File
@@ -23,6 +23,7 @@ import ifcopenshell
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim import blenderbim.bim
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
from pytest_bdd import scenarios, given, when, then, parsers from pytest_bdd import scenarios, given, when, then, parsers
from mathutils import Vector from mathutils import Vector
@@ -108,6 +109,7 @@ def i_add_a_new_collection_item(collection):
assert False, "Collection does not exist" assert False, "Collection does not exist"
@given(parsers.parse('the material "{name}" colour is set to "{colour}"')) @given(parsers.parse('the material "{name}" colour is set to "{colour}"'))
@when(parsers.parse('the material "{name}" colour is set to "{colour}"')) @when(parsers.parse('the material "{name}" colour is set to "{colour}"'))
def the_material_name_colour_is_set_to_colour(name, colour): def the_material_name_colour_is_set_to_colour(name, colour):
@@ -507,7 +509,16 @@ def the_collection_name1_is_in_the_collection_name2(name1, name2):
@then(parsers.parse('the object "{name}" does not exist')) @then(parsers.parse('the object "{name}" does not exist'))
def the_object_name_does_not_exist(name): def the_object_name_does_not_exist(name):
assert bpy.data.objects.get(name) is None, "Object exists" obj = bpy.data.objects.get(name)
assert obj is None or len(obj.users_collection) == 0, "Object exists"
@then(parsers.parse('objects starting with "{name}" do not exist'))
def objects_not_exist_starting_with(name):
objs = [obj for obj in bpy.data.objects if obj.name.startswith(name) and len(obj.users_collection) > 0]
if len(objs) > 0:
assert False, f'{len(objs)} objects starting with "{name}" exist'
assert True
@then(parsers.parse('the object "{name}" is at "{location}"')) @then(parsers.parse('the object "{name}" is at "{location}"'))
@@ -562,3 +573,108 @@ def the_file_name_should_contain_value(name, value):
@then(parsers.parse('the object "{name}" has no modifiers')) @then(parsers.parse('the object "{name}" has no modifiers'))
def the_object_name_has_no_modifiers(name): def the_object_name_has_no_modifiers(name):
assert len(the_object_name_exists(name).modifiers) == 0 assert len(the_object_name_exists(name).modifiers) == 0
@then(parsers.parse('the construction type "{ifc_class}"/"{relating_type}" has a preview'))
def the_construction_type_has_a_preview(ifc_class, relating_type):
if "preview_constr_types" not in AuthoringData.data:
assert False, 'There are no previews loaded'
preview_constr_types = AuthoringData.data["preview_constr_types"]
if ifc_class not in preview_constr_types:
assert False, f'Construction class {ifc_class} has no available previews'
relating_type_id = AuthoringData.relating_type_id_by_name(ifc_class, relating_type)
if relating_type_id is None:
assert False, f'No construction type {ifc_class}/{relating_type} was found'
if relating_type_id not in preview_constr_types[ifc_class]:
assert False, f'Construction type {ifc_class}/{relating_type} has no available previews'
preview_data = preview_constr_types[ifc_class][relating_type_id]
if 'icon_id' not in preview_data:
assert False, f'Construction type {ifc_class}/{relating_type} has a preview, but no assigned icon_id'
icon_id = preview_data["icon_id"]
if not isinstance(icon_id, int):
assert False, f'Construction type {ifc_class}/{relating_type} has an invalid icon_id {icon_id}'
# Note: icon_id must be > 0 in UI mode, but asset_generate_preview() doesn't work headlessly -> skipping for now
# if icon_id == 0:
# assert False, f'Construction type {ifc_class}/{relating_type} has the default null value for icon_id'
assert True
@then("there is a Construction Type preview")
def there_is_a_construction_type_preview():
props = bpy.context.scene.BIMModelProperties
assert props.icon_id > 0, f"There isn't a Construction Type preview"
@then(parsers.parse('all construction types for "{ifc_class}" have a preview'))
def all_construction_types_have_a_preview(ifc_class):
if "preview_constr_types" not in AuthoringData.data:
assert False, 'There are no previews loaded'
preview_constr_types = AuthoringData.data["preview_constr_types"]
if ifc_class not in preview_constr_types:
assert False, f'Construction class {ifc_class} has no available previews'
constr_class_occurrences = AuthoringData.constr_class_entities(ifc_class)
for constr_class_entity in constr_class_occurrences:
the_construction_type_has_a_preview(ifc_class, constr_class_entity.Name)
@given("I load the demo construction library")
@when("I load the demo construction library")
def i_add_a_construction_library():
lib_path = './blenderbim/bim/data/libraries/IFC4 Demo Library.ifc'
bpy.ops.bim.select_library_file(filepath=lib_path, append_all=True)
@given("I display the construction type browser")
@when("I display the construction type browser")
def i_display_the_construction_type_browser():
bpy.ops.bim.display_relating_types('INVOKE_DEFAULT')
@given("I preview only one asset on the construction type browser")
@when("I preview only one asset on the construction type browser")
def i_preview_one_construction_type():
bpy.context.scene.BIMModelProperties.unfold_relating_types = False
@given("I preview all available assets on the construction type browser")
@when("I preview all available assets on the construction type browser")
def i_preview_all_construction_types():
bpy.context.scene.BIMModelProperties.unfold_relating_types = True
@given("I select the browser construction type")
@when("I select the browser construction type")
def i_select_the_active_construction_type():
props = bpy.context.scene.BIMModelProperties
bpy.ops.bim.select_construction_type(
ifc_class=props.ifc_class_browser, relating_type_id=props.relating_type_id_browser
)
@given("I add the browser construction type")
@when("I add the browser construction type")
def i_add_the_active_construction_type():
props = bpy.context.scene.BIMModelProperties
bpy.ops.bim.add_constr_type_instance(
ifc_class=props.ifc_class_browser, relating_type_id=int(props.relating_type_id_browser)
)
@then(parsers.parse("browser construction type is {relating_type_name}"))
def browser_construction_type(relating_type_name):
props = bpy.context.scene.BIMModelProperties
relating_type_browser = AuthoringData.relating_type_name_by_id(props.ifc_class_browser, props.relating_type_id_browser)
assert relating_type_browser == relating_type_name, (f"Construction Type is a {relating_type_browser}, not " +
f"a {relating_type_name}")
@then(parsers.parse("construction type is {relating_type_name}"))
def construction_type(relating_type_name):
props = bpy.context.scene.BIMModelProperties
relating_type = AuthoringData.relating_type_name_by_id(props.ifc_class, props.relating_type_id)
assert relating_type == relating_type_name, f"Construction Type is a {relating_type}, not a {relating_type_name}"
@when("I move the cursor to the bottom left corner")
def move_cursor_bottom_left():
bpy.context.window.cursor_warp(10, 10)