From 58e9f1baf986e26d0607b0b2ac6e69d10f284e2d Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 10 May 2021 14:06:18 +0800 Subject: [PATCH] merage --- src/blenderbim/blenderbim/bim/helper.py | 201 +++------ src/blenderbim/blenderbim/bim/import_ifc.py | 2 +- .../bim/module/geometry/operator.py | 2 +- .../blenderbim/bim/module/root/operator.py | 6 +- .../bim/module/sequence/__init__.py | 2 + .../bim/module/sequence/operator.py | 384 ++++++------------ .../blenderbim/bim/module/sequence/prop.py | 2 + .../blenderbim/bim/module/sequence/ui.py | 8 +- .../bim/module/structural/__init__.py | 17 +- .../bim/module/structural/operator.py | 274 ++++++++++--- .../blenderbim/bim/module/structural/prop.py | 59 ++- .../blenderbim/bim/module/structural/ui.py | 114 +++++- src/ifcgeom/IfcGeomTree.h | 11 +- 13 files changed, 593 insertions(+), 489 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 218eb4294d..e445aa67fb 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -1,153 +1,64 @@ +import bpy +import json import math +import ifcopenshell +import ifcopenshell.util.attribute from mathutils import geometry from mathutils import Vector -import bpy +from blenderbim.bim.ifc import IfcStore -# TODO: Deprecate this in favour of ifcopenshell.util.unit +def import_attributes(ifc_class, props, data, callback=None): + for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)): + continue + new = props.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + new.is_optional = attribute.optional() + new.data_type = data_type if isinstance(data_type, str) else "" + is_handled_by_callback = callback(attribute.name(), new, data) if callback else False + if is_handled_by_callback: + pass # Our job is done + elif is_handled_by_callback is False: + props.remove(len(props) - 1) + elif data_type == "string": + new.string_value = "" if new.is_null else data[attribute.name()] + elif data_type == "boolean": + new.bool_value = False if new.is_null else data[attribute.name()] + elif data_type == "integer": + new.int_value = 0 if new.is_null else data[attribute.name()] + elif data_type == "float": + new.float_value = 0.0 if new.is_null else data[attribute.name()] + elif data_type == "enum": + new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) + if data[attribute.name()]: + new.enum_value = data[attribute.name()] -class SIUnitHelper: - prefixes = { - "EXA": 1e18, - "PETA": 1e15, - "TERA": 1e12, - "GIGA": 1e9, - "MEGA": 1e6, - "KILO": 1e3, - "HECTO": 1e2, - "DECA": 1e1, - "DECI": 1e-1, - "CENTI": 1e-2, - "MILLI": 1e-3, - "MICRO": 1e-6, - "NANO": 1e-9, - "PICO": 1e-12, - "FEMTO": 1e-15, - "ATTO": 1e-18, - } - unit_names = [ - "AMPERE", - "BECQUEREL", - "CANDELA", - "COULOMB", - "CUBIC_METRE", - "DEGREE CELSIUS", - "FARAD", - "GRAM", - "GRAY", - "HENRY", - "HERTZ", - "JOULE", - "KELVIN", - "LUMEN", - "LUX", - "MOLE", - "NEWTON", - "OHM", - "PASCAL", - "RADIAN", - "SECOND", - "SIEMENS", - "SIEVERT", - "SQUARE METRE", - "METRE", - "STERADIAN", - "TESLA", - "VOLT", - "WATT", - "WEBER", - ] - si_conversions = { - "inch": 0.0254, - "foot": 0.3048, - "yard": 0.914, - "mile": 1609, - "square inch": 0.0006452, - "square foot": 0.09290304, - "square yard": 0.83612736, - "acre": 4046.86, - "square mile": 2588881, - "cubic inch": 0.00001639, - "cubic foot": 0.02831684671168849, - "cubic yard": 0.7636, - "litre": 0.001, - "fluid ounce UK": 0.0000284130625, - "fluid ounce US": 0.00002957353, - "pint UK": 0.000568, - "pint US": 0.000473, - "gallon UK": 0.004546, - "gallon US": 0.003785, - "degree": math.pi / 180, - "ounce": 0.02835, - "pound": 0.454, - "ton UK": 1016.0469088, - "ton US": 907.18474, - "lbf": 4.4482216153, - "kip": 4448.2216153, - "psi": 6894.7572932, - "ksi": 6894757.2932, - "minute": 60, - "hour": 3600, - "day": 86400, - "btu": 1055.056, - } +def export_attributes(props, callback=None): + attributes = {} + for attribute in props: + is_handled_by_callback = callback(attributes, attribute) if callback else False + if attribute.is_null: + attributes[attribute.name] = None + elif is_handled_by_callback: + pass # Our job is done + elif attribute.data_type == "string": + attributes[attribute.name] = attribute.string_value + elif attribute.data_type == "boolean": + attributes[attribute.name] = attribute.bool_value + elif attribute.data_type == "integer": + attributes[attribute.name] = attribute.int_value + elif attribute.data_type == "float": + attributes[attribute.name] = attribute.float_value + elif attribute.data_type == "enum": + attributes[attribute.name] = attribute.enum_value + return attributes - @staticmethod - def get_prefix(text): - for prefix in SIUnitHelper.prefixes.keys(): - if prefix in text.upper(): - return prefix - @staticmethod - def get_prefix_multiplier(text): - if not text: - return 1 - prefix = SIUnitHelper.get_prefix(text) - if prefix: - return SIUnitHelper.prefixes[prefix] - return 1 - - @staticmethod - def get_unit_name(text): - for name in SIUnitHelper.unit_names: - if name in text.upper().replace("METER", "METRE"): - return name - - @staticmethod - def convert(value, from_prefix, from_unit, to_prefix, to_unit): - """Converts between length, area, and volume units - - :param value: The numeric value you want to convert - :type value: float - :param from_prefix: A prefix from IfcSIPrefix. Can be None. - :type from_prefix: string - :param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type from_unit: string - :param to_prefix: A prefix from IfcSIPrefix. Can be None. - :type to_prefix: string - :param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type to_unit: string - """ - if from_unit in SIUnitHelper.si_conversions: - value *= SIUnitHelper.si_conversions[from_unit] - elif from_prefix: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - if "SQUARE" in from_unit: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - elif "CUBIC" in from_unit: - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - if to_unit in SIUnitHelper.si_conversions: - return value * (1 / SIUnitHelper.si_conversions[to_unit]) - elif to_prefix: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - if "SQUARE" in from_unit: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - elif "CUBIC" in from_unit: - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) - return value +# TODO: migrate the below helper functions into the drawing module, since it is specific to that module # This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py @@ -302,7 +213,7 @@ def parse_diagram_scale(camera): def get_project_collection(scene): """Get main project collection""" - colls = [c for c in scene.collection.children if c.name.startswith('IfcProject')] + colls = [c for c in scene.collection.children if c.name.startswith("IfcProject")] if len(colls) != 1: raise RuntimeError("project collection missing or not unique") return colls[0] @@ -315,7 +226,7 @@ def get_active_drawing(scene): return None, None try: drawing = props.drawings[props.active_drawing_index] - return scene.collection.children['Views'].children[f"IfcGroup/{drawing.name}"], drawing.camera + return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera except (KeyError, IndexError): raise RuntimeError("missing drawing collection") @@ -333,8 +244,8 @@ def ortho_view_frame(camera, margin=0.015): """ aspect = camera.BIMCameraProperties.raster_y / camera.BIMCameraProperties.raster_x size = camera.ortho_scale - hwidth = size * .5 - hheight = size * .5 * aspect + hwidth = size * 0.5 + hheight = size * 0.5 * aspect scale = parse_diagram_scale(camera) xmarg = margin * scale ymarg = margin * scale * aspect diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 9d1b5a5ae9..6ec10e5145 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -718,7 +718,7 @@ class IfcImporter: # Create structural collections self.structural_member_collection = bpy.data.collections.new("Members") self.structural_connection_collection = bpy.data.collections.new("Connections") - self.structural_collection = bpy.data.collections.new("StructuralEntities") + self.structural_collection = bpy.data.collections.new("StructuralItems") self.structural_collection.children.link(self.structural_member_collection) self.structural_collection.children.link(self.structural_connection_collection) self.project["blender"].children.link(self.structural_collection) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 0047d012eb..3c41163b54 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -122,7 +122,7 @@ class AddRepresentation(bpy.types.Operator): [ bpy.ops.bim.add_style(material=s.material.name) for s in obj.material_slots - if not s.material.BIMMaterialProperties.ifc_style_id + if s.material and not s.material.BIMMaterialProperties.ifc_style_id ] ifcopenshell.api.run( diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 19e6f07a7d..f345282471 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -76,7 +76,7 @@ class ReassignClass(bpy.types.Operator): class AssignClass(bpy.types.Operator): bl_idname = "bim.assign_class" bl_label = "Assign IFC Class" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() ifc_class: bpy.props.StringProperty() predefined_type: bpy.props.StringProperty() @@ -92,10 +92,6 @@ class AssignClass(bpy.types.Operator): elif self.predefined_type == "": predefined_type = None for obj in objects: - if obj.data and hasattr(obj.data, "materials"): - for material in obj.data.materials: - if not material.BIMMaterialProperties.ifc_style_id: - bpy.ops.bim.add_style(material=material.name) self.assign_class(context, obj) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 379532fa0c..bce21e7b78 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -61,6 +61,7 @@ classes = ( operator.UnassignProduct, operator.GenerateGanttChart, operator.ImportP6, + operator.ImportMSP, operator.LoadTaskProperties, operator.SelectTaskRelatedProducts, operator.VisualiseWorkScheduleDate, @@ -82,6 +83,7 @@ classes = ( def menu_func_import(self, context): self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)") + self.layout.operator(operator.ImportMSP.bl_idname, text="Microsoft Project (.xml)") def register(): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 88cdece0e5..73d649c05d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -1,3 +1,4 @@ +import re import os import bpy import json @@ -7,6 +8,7 @@ import pystache import webbrowser import ifcopenshell.api import ifcopenshell.util.date +import blenderbim.bim.helper import blenderbim.bim.module.sequence.helper as helper from datetime import datetime from datetime import timedelta @@ -32,15 +34,7 @@ class EditWorkPlan(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkPlanProperties - attributes = {} - for attribute in props.work_plan_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.work_plan_attributes, self.export_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_plan", @@ -51,6 +45,14 @@ class EditWorkPlan(bpy.types.Operator): bpy.ops.bim.disable_editing_work_plan() return {"FINISHED"} + def export_attributes(self, attributes, prop): + if "Date" in prop.name or "Time" in prop.name: + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "Duration" or prop.name == "TotalFloat": + attributes[prop.name] = helper.parse_duration(prop.string_value) + return True + class RemoveWorkPlan(bpy.types.Operator): bl_idname = "bim.remove_work_plan" @@ -76,27 +78,17 @@ class EnableEditingWorkPlan(bpy.types.Operator): data = Data.work_plans[self.work_plan] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkPlan").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.work_plan_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if attribute.name() in ["CreationDate", "StartTime", "FinishTime"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcWorkPlan", props.work_plan_attributes, data, self.import_attributes) + props.active_work_plan_id = self.work_plan props.editing_type = "ATTRIBUTES" return {"FINISHED"} + def import_attributes(self, name, prop, data): + if name in ["CreationDate", "StartTime", "FinishTime"]: + prop.string_value = "" if prop.is_null else data[name].isoformat() + return True + class DisableEditingWorkPlan(bpy.types.Operator): bl_idname = "bim.disable_editing_work_plan" @@ -175,15 +167,7 @@ class EditWorkSchedule(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.work_schedule_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, self.export_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_schedule", @@ -194,6 +178,14 @@ class EditWorkSchedule(bpy.types.Operator): bpy.ops.bim.disable_editing_work_schedule() return {"FINISHED"} + def export_attributes(self, attributes, prop): + if "Date" in prop.name or "Time" in prop.name: + attributes[prop.name] = helper.parse_datetime(prop.string_value) + return True + elif prop.name == "Duration" or prop.name == "TotalFloat": + attributes[prop.name] = helper.parse_duration(prop.string_value) + return True + class RemoveWorkSchedule(bpy.types.Operator): bl_idname = "bim.remove_work_schedule" @@ -226,23 +218,14 @@ class EnableEditingWorkSchedule(bpy.types.Operator): def enable_editing_work_schedule(self): data = Data.work_schedules[self.work_schedule] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_schedule_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if attribute.name() in ["CreationDate", "StartTime", "FinishTime"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes( + "IfcWorkSchedule", self.props.work_schedule_attributes, data, self.import_attributes + ) + + def import_attributes(self, name, prop, data): + if name in ["CreationDate", "StartTime", "FinishTime"]: + prop.string_value = "" if prop.is_null else data[name].isoformat() + return True class EnableEditingTasks(bpy.types.Operator): @@ -258,10 +241,11 @@ class EnableEditingTasks(bpy.types.Operator): self.tprops.tasks.remove(0) self.contracted_tasks = json.loads(self.props.contracted_tasks) - sort_keys = { - i: Data.tasks[i]["Identification"] for i in Data.work_schedules[self.work_schedule]["RelatedObjects"] + self.sort_keys = { + i: Data.tasks[i]["Identification"] or "" for i in Data.work_schedules[self.work_schedule]["RelatedObjects"] } - for related_object_id in sorted(sort_keys, key=sort_keys.__getitem__): + + for related_object_id in sorted(self.sort_keys, key=self.natural_sort_key): self.create_new_task_li(related_object_id, 0) bpy.ops.bim.load_task_properties() self.props.editing_type = "TASKS" @@ -276,10 +260,13 @@ class EnableEditingTasks(bpy.types.Operator): if task["RelatedObjects"]: new.has_children = True if new.is_expanded: - sort_keys = {i: Data.tasks[i]["Identification"] for i in task["RelatedObjects"]} - for related_object_id in sorted(sort_keys, key=sort_keys.__getitem__): + self.sort_keys = {i: Data.tasks[i]["Identification"] or "" for i in task["RelatedObjects"]} + for related_object_id in sorted(self.sort_keys, key=self.natural_sort_key): self.create_new_task_li(related_object_id, level_index + 1) - return {"FINISHED"} + + def natural_sort_key(self, i, _nsre=re.compile("([0-9]+)")): + s = self.sort_keys[i] + return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] class LoadTaskProperties(bpy.types.Operator): @@ -322,6 +309,10 @@ class LoadTaskProperties(bpy.types.Operator): item.start = "-" item.finish = "-" item.duration = "-" + if task["HasAssignmentsWorkCalendar"]: + item.calendar = Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]]["Name"] or "Unnamed" + else: + item.calendar = "" self.props.is_task_update_enabled = True return {"FINISHED"} @@ -434,39 +425,23 @@ class EnableEditingTaskTime(bpy.types.Operator): data = Data.task_times[task_time_id] - for attribute in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.task_time_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - if isinstance(data[attribute.name()], datetime): - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif isinstance(data[attribute.name()], isodate.Duration): - new.string_value = ( - "" - if new.is_null - else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") - ) - else: - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else data[attribute.name()] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcTaskTime", props.task_time_attributes, data, self.import_attributes) props.active_task_time_id = task_time_id props.active_task_id = self.task props.editing_task_type = "TASKTIME" return {"FINISHED"} + def import_attributes(self, name, prop, data): + if prop.data_type == "string": + if isinstance(data[name], datetime): + prop.string_value = "" if prop.is_null else data[name].isoformat() + return True + elif isinstance(data[name], isodate.Duration): + prop.string_value = ( + "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration") + ) + return True + def add_task_time(self): task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.file.by_id(self.task)) Data.load(IfcStore.get_file()) @@ -479,20 +454,7 @@ class EditTaskTime(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.task_time_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "boolean": - attributes[attribute.name] = attribute.bool_value - elif attribute.data_type == "float": - attributes[attribute.name] = attribute.float_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value - + attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_attributes) attributes = self.convert_strings_to_date_times(attributes) self.file = IfcStore.get_file() @@ -506,21 +468,13 @@ class EditTaskTime(bpy.types.Operator): bpy.ops.bim.load_task_properties(task=props.active_task_id) return {"FINISHED"} - def convert_strings_to_date_times(self, attributes): - for key, value in attributes.items(): - if not value: - continue - if "Start" in key or "Finish" in key or key == "StatusTime": - try: - attributes[key] = parser.isoparse(value) - except: - try: - attributes[key] = parser.parse(value, dayfirst=True, fuzzy=True) - except: - attributes[key] = None - elif key == "ScheduleDuration": - attributes[key] = isodate.parse_duration(value) - return attributes + def export_attributes(self, attributes, prop): + if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": + attributes[prop.name] = helper.parse_datetime(value) + return True + elif prop.name == "ScheduleDuration": + attributes[prop.name] = helper.parse_duration(value) + return True class EnableEditingTask(bpy.types.Operator): @@ -535,25 +489,7 @@ class EnableEditingTask(bpy.types.Operator): data = Data.tasks[self.task] - for attribute in IfcStore.get_schema().declaration_by_name("IfcTask").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.task_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else data[attribute.name()] - elif data_type == "integer": - new.int_value = 0 if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcTask", props.task_attributes, data) props.active_task_id = self.task props.editing_task_type = "ATTRIBUTES" return {"FINISHED"} @@ -575,19 +511,7 @@ class EditTask(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.task_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "boolean": - attributes[attribute.name] = attribute.bool_value - elif attribute.data_type == "integer": - attributes[attribute.name] = attribute.int_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.export_attributes(props.task_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_task", self.file, **{"task": self.file.by_id(props.active_task_id), "attributes": attributes} @@ -773,15 +697,7 @@ class EditWorkCalendar(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkCalendarProperties - attributes = {} - for attribute in props.work_calendar_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.work_calendar_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_calendar", @@ -819,21 +735,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator): data = Data.work_calendars[self.work_calendar] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkCalendar").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_calendar_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcWorkCalendar", self.props.work_calendar_attributes, data) self.props.active_work_calendar_id = self.work_calendar self.props.editing_type = "ATTRIBUTES" return {"FINISHED"} @@ -869,6 +771,27 @@ class ImportP6(bpy.types.Operator, ImportHelper): return {"FINISHED"} +class ImportMSP(bpy.types.Operator, ImportHelper): + bl_idname = "import_msp.bim" + bl_label = "Import MSP" + filename_ext = ".xml" + filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"}) + + def execute(self, context): + from ifcp6.msp2ifc import MSP2Ifc + + self.file = IfcStore.get_file() + start = time.time() + msp2ifc = MSP2Ifc() + msp2ifc.xml = self.filepath + msp2ifc.file = self.file + msp2ifc.work_plan = self.file.by_type("IfcWorkPlan")[0] if self.file.by_type("IfcWorkPlan") else None + msp2ifc.execute() + Data.load(IfcStore.get_file()) + print("Import finished in {:.2f} seconds".format(time.time() - start)) + return {"FINISHED"} + + class EnableEditingWorkCalendarTimes(bpy.types.Operator): bl_idname = "bim.enable_editing_work_calendar_times" bl_label = "Enable Editing Work Calendar Times" @@ -910,29 +833,20 @@ class EnableEditingWorkTime(bpy.types.Operator): data = Data.work_times[self.work_time] - for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkTime").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.work_time_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if attribute.name() in ["Start", "Finish"]: - new.string_value = "" if new.is_null else data[attribute.name()].isoformat() - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes( + "IfcWorkTime", self.props.work_time_attributes, data, self.import_attributes + ) self.initialise_recurrence_components() self.load_recurrence_pattern_data(data) self.props.active_work_time_id = self.work_time return {"FINISHED"} + def import_attributes(self, name, prop, data): + if name in ["Start", "Finish"]: + prop.string_value = "" if prop.is_null else data[name].isoformat() + return True + def initialise_recurrence_components(self): if len(self.props.day_components) == 0: for i in range(0, 31): @@ -988,15 +902,7 @@ class EditWorkTime(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMWorkCalendarProperties - attributes = {} - for attribute in self.props.work_time_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(self.props.work_time_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_work_time", @@ -1163,6 +1069,7 @@ class EditTaskCalendar(bpy.types.Operator): }, ) Data.load(IfcStore.get_file()) + bpy.ops.bim.load_task_properties(task=self.task) return {"FINISHED"} @@ -1183,6 +1090,7 @@ class RemoveTaskCalendar(bpy.types.Operator): }, ) Data.load(IfcStore.get_file()) + bpy.ops.bim.load_task_properties(task=self.task) return {"FINISHED"} @@ -1225,21 +1133,7 @@ class EnableEditingSequenceAttributes(bpy.types.Operator): def enable_editing_sequence_attributes(self): data = Data.sequences[self.sequence] - for attribute in IfcStore.get_schema().declaration_by_name("IfcRelSequence").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.sequence_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes("IfcRelSequence", self.props.sequence_attributes, data) class EnableEditingSequenceTimeLag(bpy.types.Operator): @@ -1259,34 +1153,22 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator): def enable_editing_attributes(self): data = Data.lag_times[self.lag_time] - for attribute in IfcStore.get_schema().declaration_by_name("IfcLagTime").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.time_lag_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if attribute.name() == "LagValue": - if isinstance(data[attribute.name()], isodate.Duration): - new.data_type = "string" - new.string_value = ( - "" - if new.is_null - else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") - ) - else: - new.data_type = "float" - new.float_value = 0.0 if new.is_null else data[attribute.name()] - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + blenderbim.bim.helper.import_attributes( + "IfcLagTime", self.props.time_lag_attributes, data, self.import_attributes + ) + + def import_attributes(self, name, prop, data): + if name == "LagValue": + if isinstance(data[name], isodate.Duration): + prop.data_type = "string" + prop.string_value = ( + "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration") + ) + return True + else: + prop.data_type = "float" + prop.float_value = 0.0 if prop.is_null else data[name] + return True class UnassignLagTime(bpy.types.Operator): @@ -1325,15 +1207,7 @@ class EditSequenceAttributes(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.sequence_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.sequence_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_sequence", @@ -1352,17 +1226,7 @@ class EditSequenceTimeLag(bpy.types.Operator): def execute(self, context): props = context.scene.BIMWorkScheduleProperties - attributes = {} - for attribute in props.time_lag_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "float": - attributes[attribute.name] = attribute.float_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.time_lag_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "sequence.edit_lag_time", diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index ad51aff00d..4a5a750939 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -159,6 +159,7 @@ class Task(PropertyGroup): derived_start: StringProperty(name="Derived Start") derived_finish: StringProperty(name="Derived Finish") derived_duration: StringProperty(name="Derived Duration") + calendar: StringProperty(name="Calendar") is_predecessor: BoolProperty(name="Is Predecessor") is_successor: BoolProperty(name="Is Successor") @@ -189,6 +190,7 @@ class BIMWorkScheduleProperties(PropertyGroup): task_attributes: CollectionProperty(name="Task Attributes", type=Attribute) should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False) should_show_times: BoolProperty(name="Should Show Times", default=False) + should_show_calendars: BoolProperty(name="Should Show Calendars", default=False) active_task_time_id: IntProperty(name="Active Task Id") task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute) contracted_tasks: StringProperty(name="Contracted Task Items", default="[]") diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index ea5aab4a13..0a60b91168 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -109,6 +109,7 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK") elif self.props.editing_type == "TASKS": row.prop(self.props, "should_show_times", text="", icon="TIME") + row.prop(self.props, "should_show_calendars", text="", icon="VIEW_ORTHO") row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO") row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id @@ -336,6 +337,9 @@ class BIM_UL_tasks(UIList): else: row.prop(item, "duration", emboss=False, text="") + if props.should_show_calendars: + row.label(text=item.calendar) + if context.active_object: oprops = context.active_object.BIMObjectProperties row = layout.row(align=True) @@ -349,10 +353,6 @@ class BIM_UL_tasks(UIList): if props.active_task_id == item.ifc_definition_id: if props.editing_task_type == "TASKTIME": row.operator("bim.edit_task_time", text="", icon="CHECKMARK") - elif props.editing_task_type == "CALENDAR": - row.operator("bim.disable_editing_task", text="", icon="CHECKMARK") - elif props.editing_task_type == "SEQUENCE": - row.operator("bim.disable_editing_task", text="", icon="CHECKMARK") elif props.editing_task_type == "ATTRIBUTES": row.operator("bim.edit_task", text="", icon="CHECKMARK") row.operator("bim.disable_editing_task", text="", icon="CANCEL") diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index 23c81445d0..20e5e28500 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -20,22 +20,33 @@ classes = ( operator.EnableEditingStructuralConnectionCondition, operator.DisableEditingStructuralConnectionCondition, operator.RemoveStructuralConnectionCondition, - operator.EnableEditingStructuralMemberAxis, - operator.DisableEditingStructuralMemberAxis, - operator.EditStructuralMemberAxis, + operator.EnableEditingStructuralItemAxis, + operator.DisableEditingStructuralItemAxis, + operator.EditStructuralItemAxis, + operator.EnableEditingStructuralItemConnectionCS, + operator.DisableEditingStructuralItemConnectionCS, + operator.EditStructuralItemConnectionCS, operator.AddStructuralLoadCase, operator.EditStructuralLoadCase, operator.RemoveStructuralLoadCase, + operator.AddStructuralLoadGroup, + operator.RemoveStructuralLoadGroup, + operator.AddStructuralActivity, operator.EnableEditingStructuralLoadCase, + operator.EnableEditingStructuralLoadCaseGroups, operator.DisableEditingStructuralLoadCase, + operator.EnableEditingStructuralLoadGroupActivities, prop.StructuralAnalysisModel, + prop.StructuralActivity, prop.BIMStructuralProperties, prop.BIMObjectStructuralProperties, ui.BIM_PT_structural_analysis_models, ui.BIM_PT_structural_boundary_conditions, ui.BIM_PT_connected_structural_members, ui.BIM_PT_structural_member, + ui.BIM_PT_structural_connection, ui.BIM_UL_structural_analysis_models, + ui.BIM_UL_structural_activities, ui.BIM_PT_structural_load_cases, ) diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 4fdb84203b..845f023b1f 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -2,6 +2,7 @@ import bpy import json import ifcopenshell import ifcopenshell.api +import blenderbim.bim.helper from math import degrees from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore @@ -259,7 +260,7 @@ class EditStructuralAnalysisModel(bpy.types.Operator): **{ "structural_analysis_model": self.file.by_id(props.active_structural_analysis_model_id), "attributes": attributes, - } + }, ) Data.load(IfcStore.get_file()) bpy.ops.bim.load_structural_analysis_models() @@ -277,7 +278,7 @@ class RemoveStructuralAnalysisModel(bpy.types.Operator): ifcopenshell.api.run( "structural.remove_structural_analysis_model", self.file, - **{"structural_analysis_model": self.file.by_id(self.structural_analysis_model)} + **{"structural_analysis_model": self.file.by_id(self.structural_analysis_model)}, ) Data.load(IfcStore.get_file()) bpy.ops.bim.load_structural_analysis_models() @@ -340,7 +341,7 @@ class AssignStructuralAnalysisModel(bpy.types.Operator): **{ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), "structural_analysis_model": self.file.by_id(self.structural_analysis_model), - } + }, ) Data.load(IfcStore.get_file()) return {"FINISHED"} @@ -361,15 +362,15 @@ class UnassignStructuralAnalysisModel(bpy.types.Operator): **{ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), "structural_analysis_model": self.file.by_id(self.structural_analysis_model), - } + }, ) Data.load(IfcStore.get_file()) return {"FINISHED"} -class EnableEditingStructuralMemberAxis(bpy.types.Operator): - bl_idname = "bim.enable_editing_structural_member_axis" - bl_label = "Enable Editing Structural Member Axis" +class EnableEditingStructuralItemAxis(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_item_axis" + bl_label = "Enable Editing Structural Item Axis" def execute(self, context): obj = bpy.context.active_object @@ -377,20 +378,22 @@ class EnableEditingStructuralMemberAxis(bpy.types.Operator): props = obj.BIMStructuralProperties self.file = IfcStore.get_file() - member = self.file.by_id(oprops.ifc_definition_id) - z_axis = Vector(member.Axis.DirectionRatios).normalized() @ obj.matrix_world if member.Axis else None + item = self.file.by_id(oprops.ifc_definition_id) + z_axis = Vector(item.Axis.DirectionRatios).normalized() @ obj.matrix_world if item.Axis else None x_axis = (obj.data.vertices[1].co - obj.data.vertices[0].co).normalized() location = obj.data.vertices[0].co - empty = bpy.data.objects.new("Member Axis", None) + empty = bpy.data.objects.new("Item Axis", None) empty.empty_display_type = "ARROWS" if z_axis: y_axis = (z_axis.cross(x_axis)).normalized() - empty.matrix_world = Matrix(( - (x_axis[0], y_axis[0], z_axis[0], location[0]), - (x_axis[1], y_axis[1], z_axis[1], location[1]), - (x_axis[2], y_axis[2], z_axis[2], location[2]), - (0, 0, 0, 1), - )) + empty.matrix_world = Matrix( + ( + (x_axis[0], y_axis[0], z_axis[0], location[0]), + (x_axis[1], y_axis[1], z_axis[1], location[1]), + (x_axis[2], y_axis[2], z_axis[2], location[2]), + (0, 0, 0, 1), + ) + ) else: empty.location = location empty.rotation_mode = "QUATERNION" @@ -404,9 +407,9 @@ class EnableEditingStructuralMemberAxis(bpy.types.Operator): return {"FINISHED"} -class DisableEditingStructuralMemberAxis(bpy.types.Operator): - bl_idname = "bim.disable_editing_structural_member_axis" - bl_label = "Disable Editing Structural Member Axis" +class DisableEditingStructuralItemAxis(bpy.types.Operator): + bl_idname = "bim.disable_editing_structural_item_axis" + bl_label = "Disable Editing Structural Item Axis" def execute(self, context): obj = bpy.context.active_object @@ -417,9 +420,9 @@ class DisableEditingStructuralMemberAxis(bpy.types.Operator): return {"FINISHED"} -class EditStructuralMemberAxis(bpy.types.Operator): - bl_idname = "bim.edit_structural_member_axis" - bl_label = "Edit Structural Member Axis" +class EditStructuralItemAxis(bpy.types.Operator): + bl_idname = "bim.edit_structural_item_axis" + bl_label = "Edit Structural Item Axis" def execute(self, context): obj = bpy.context.active_object @@ -429,12 +432,85 @@ class EditStructuralMemberAxis(bpy.types.Operator): z_axis = relative_matrix.col[2][0:3] self.file = IfcStore.get_file() ifcopenshell.api.run( - "structural.edit_structural_member_axis", + "structural.edit_structural_item_axis", self.file, - structural_member=self.file.by_id(oprops.ifc_definition_id), + structural_item=self.file.by_id(oprops.ifc_definition_id), axis=z_axis, ) - bpy.ops.bim.disable_editing_structural_member_axis() + bpy.ops.bim.disable_editing_structural_item_axis() + return {"FINISHED"} + + +class EnableEditingStructuralItemConnectionCS(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_item_connection_cs" + bl_label = "Enable Editing Structural Item Connection CS" + + def execute(self, context): + obj = bpy.context.active_object + oprops = obj.BIMObjectProperties + props = obj.BIMStructuralProperties + + self.file = IfcStore.get_file() + item = self.file.by_id(oprops.ifc_definition_id) + z_axis = Vector(item.Axis.DirectionRatios).normalized() @ obj.matrix_world if item.Axis else None + x_axis = (obj.data.vertices[1].co - obj.data.vertices[0].co).normalized() + location = obj.data.vertices[0].co + empty = bpy.data.objects.new("Item Connection CS", None) + empty.empty_display_type = "ARROWS" + if z_axis: + y_axis = (z_axis.cross(x_axis)).normalized() + empty.matrix_world = Matrix( + ( + (x_axis[0], y_axis[0], z_axis[0], location[0]), + (x_axis[1], y_axis[1], z_axis[1], location[1]), + (x_axis[2], y_axis[2], z_axis[2], location[2]), + (0, 0, 0, 1), + ) + ) + else: + empty.location = location + empty.rotation_mode = "QUATERNION" + empty.rotation_quaternion = x_axis.to_track_quat("X", "Z") + + props.axis_angle = degrees(empty.rotation_euler[0]) + props.axis_empty = empty + context.scene.collection.objects.link(empty) + + props.is_editing_axis = True + return {"FINISHED"} + + +class DisableEditingStructuralItemConnectionCS(bpy.types.Operator): + bl_idname = "bim.disable_editing_structural_item_connection_cs" + bl_label = "Disable Editing Structural Item Connection CS" + + def execute(self, context): + obj = bpy.context.active_object + props = obj.BIMStructuralProperties + props.is_editing_axis = False + if props.axis_empty: + bpy.data.objects.remove(props.axis_empty) + return {"FINISHED"} + + +class EditStructuralItemConnectionCS(bpy.types.Operator): + bl_idname = "bim.edit_structural_item_connection_cs" + bl_label = "Edit Structural Item Connection CS" + + def execute(self, context): + obj = bpy.context.active_object + oprops = obj.BIMObjectProperties + props = obj.BIMStructuralProperties + relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted() + z_axis = relative_matrix.col[2][0:3] + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "structural.edit_structural_item_connection_cs", + self.file, + structural_item=self.file.by_id(oprops.ifc_definition_id), + axis=z_axis, + ) + bpy.ops.bim.disable_editing_structural_item_axis() return {"FINISHED"} @@ -494,15 +570,7 @@ class EditStructuralLoadCase(bpy.types.Operator): def execute(self, context): props = context.scene.BIMStructuralProperties - attributes = {} - for attribute in props.load_case_attributes: - if attribute.is_null: - attributes[attribute.name] = None - else: - if attribute.data_type == "string": - attributes[attribute.name] = attribute.string_value - elif attribute.data_type == "enum": - attributes[attribute.name] = attribute.enum_value + attributes = blenderbim.bim.helper.export_attributes(props.load_case_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( "structural.edit_structural_load_case", @@ -536,33 +604,18 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMStructuralProperties self.props.active_load_case_id = self.load_case + self.props.load_case_editing_type = "ATTRIBUTES" while len(self.props.load_case_attributes) > 0: self.props.load_case_attributes.remove(0) - self.enable_editing_structural_load_case() + data = Data.load_cases[self.load_case] + blenderbim.bim.helper.import_attributes( + "IfcStructuralLoadCase", self.props.load_case_attributes, data, self.import_attributes + ) return {"FINISHED"} - def enable_editing_structural_load_case(self): - data = Data.load_cases[self.load_case] - print(data) - - for attribute in IfcStore.get_schema().declaration_by_name("IfcStructuralLoadCase").all_attributes(): - if attribute.name() in ["SelfWeightCoefficients", "Coefficient"]: - continue - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = self.props.load_case_attributes.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - print(data_type) - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + def import_attributes(self, name, prop, data): + if name in ["SelfWeightCoefficients"]: + return False class DisableEditingStructuralLoadCase(bpy.types.Operator): @@ -571,4 +624,109 @@ class DisableEditingStructuralLoadCase(bpy.types.Operator): def execute(self, context): context.scene.BIMStructuralProperties.active_load_case_id = 0 - return {"FINISHED"} \ No newline at end of file + return {"FINISHED"} + + +class EnableEditingStructuralLoadCaseGroups(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_load_case_groups" + bl_label = "Enable Editing Structural Load Case Groups" + load_case: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMStructuralProperties + self.props.active_load_case_id = self.load_case + self.props.load_case_editing_type = "GROUPS" + return {"FINISHED"} + + +class AddStructuralLoadGroup(bpy.types.Operator): + bl_idname = "bim.add_structural_load_group" + bl_label = "Add Structural Load Group" + load_case: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file) + ifcopenshell.api.run("group.assign_group", self.file, product=load_group, group=self.file.by_id(self.load_case)) + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + +class RemoveStructuralLoadGroup(bpy.types.Operator): + bl_idname = "bim.remove_structural_load_group" + bl_label = "Remove Structural Load Group" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "structural.remove_structural_load_group", self.file, load_group=self.file.by_id(self.load_group) + ) + Data.load(self.file) + return {"FINISHED"} + + +class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator): + bl_idname = "bim.enable_editing_structural_load_group_activities" + bl_label = "Enable Editing Structural Load Group Activities" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + self.props = context.scene.BIMStructuralProperties + self.props.active_load_group_id = self.load_group + self.props.load_group_editing_type = "ACTIVITY" + self.load_structural_activities() + return {"FINISHED"} + + def load_structural_activities(self): + while len(self.props.load_group_activities) > 0: + self.props.load_group_activities.remove(0) + for activity_id in Data.load_groups[self.load_group]["IsGroupedBy"]: + activity = Data.structural_activities[activity_id] + new = self.props.load_group_activities.add() + new.ifc_definition_id = activity_id + new.name = self.file.by_id(activity["AssignedToStructuralItem"]).Name or "Unnamed" + new.applied_load_class = self.file.by_id(activity["AppliedLoad"]).is_a() + + +class AddStructuralActivity(bpy.types.Operator): + bl_idname = "bim.add_structural_activity" + bl_label = "Add Structural Activity" + load_group: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMStructuralProperties + self.file = IfcStore.get_file() + for obj in context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + applied_load_class = self.props.applicable_structural_activity_types + + if element.is_a("IfcStructuralPointConnection"): + if applied_load_class not in ["IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleDisplacement"]: + continue + ifc_class = "IfcStructuralPointAction" + elif element.is_a("IfcStructuralCurveMember"): + if applied_load_class != "IfcStructuralLoadLinearForce": + continue + ifc_class = "IfcStructuralLinearAction" + elif element.is_a("IfcStructuralSurfaceMember"): + if applied_load_class != "IfcStructuralLoadPlanarForce": + continue + ifc_class = "IfcStructuralPlanarAction" + + activity = ifcopenshell.api.run( + "structural.add_structural_activity", + self.file, + ifc_class=ifc_class, + applied_load=None, # TODO + structural_member=element + ) + ifcopenshell.api.run( + "group.assign_group", self.file, product=activity, group=self.file.by_id(self.load_group) + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index c05101ea84..c38feac30c 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -1,4 +1,5 @@ import bpy +from blenderbim.bim.ifc import IfcStore from math import radians from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup @@ -14,6 +15,28 @@ from bpy.props import ( ) +def getApplicableStructuralActivityTypes(self, context): + ifc_file = IfcStore.get_file() + element_classes = set( + [ + ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id).is_a() + for o in bpy.context.selected_objects + if o.BIMObjectProperties.ifc_definition_id + ] + ) + types = [("IfcStructuralLoadTemperature", "IfcStructuralLoadTemperature", "")] + if "IfcStructuralPointConnection" in element_classes: + types.extend([ + ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""), + ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", "") + ]) + if "IfcStructuralCurveMember" in element_classes: + types.append(("IfcStructuralLoadLinearForce", "IfcStructuralLoadLinearForce", "")) + if "IfcStructuralSurfaceMember" in element_classes: + types.append(("IfcStructuralLoadPlanarForce", "IfcStructuralLoadPlanarForce", "")) + return types + + def updateAxisAngle(self, context): if not self.axis_empty: return @@ -27,11 +50,29 @@ def updateAxisAngle(self, context): empty.rotation_euler[0] = radians(self.axis_angle) +def updateConnectionCS(self, context): + if not self.ccs_empty: + return + obj = context.active_object + empty = self.ccs_empty + empty.location = obj.data.vertices[0].co + empty.rotation_mode = "XYZ" + empty.rotation_euler[0] = radians(self.ccs_x_angle) + empty.rotation_euler[1] = radians(self.ccs_y_angle) + empty.rotation_euler[2] = radians(self.ccs_z_angle) + + class StructuralAnalysisModel(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") +class StructuralActivity(PropertyGroup): + name: StringProperty(name="Name") + applied_load_class: StringProperty(name="Applied Load Class") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + class BIMStructuralProperties(PropertyGroup): structural_analysis_model_attributes: CollectionProperty( name="Structural Analysis Model Attributes", type=Attribute @@ -40,12 +81,17 @@ class BIMStructuralProperties(PropertyGroup): structural_analysis_models: CollectionProperty(name="Structural Analysis Models", type=StructuralAnalysisModel) active_structural_analysis_model_index: IntProperty(name="Active Structural Analysis Model Index") active_structural_analysis_model_id: IntProperty(name="Active Structural Analysis Model Id") - - # editing_type: StringProperty(name="Editing Type") - # active_load_case_index: IntProperty(name="Active Work Schedules Index") - + load_case_editing_type: StringProperty(name="Load Case Editing Type") load_case_attributes: CollectionProperty(name="Load Case Attributes", type=Attribute) active_load_case_id: IntProperty(name="Active Load Case Id") + load_group_editing_type: StringProperty(name="Load Group Editing Type") + # load_group_attributes: CollectionProperty(name="Load Group Attributes", type=Attribute) + active_load_group_id: IntProperty(name="Active Load Group Id") + applicable_structural_activity_types: EnumProperty( + items=getApplicableStructuralActivityTypes, name="Applicable Structural Activity Types" + ) + load_group_activities: CollectionProperty(name="Load Group Activities", type=StructuralActivity) + active_load_group_activity_index: IntProperty(name="Active Load Group Activity Index") class BIMObjectStructuralProperties(PropertyGroup): @@ -58,3 +104,8 @@ class BIMObjectStructuralProperties(PropertyGroup): axis_empty: PointerProperty(name="Axis Empty", type=bpy.types.Object) # relating_structural_activity: PointerProperty(name="Relating Structural Activity", type=bpy.types.Object) + is_editing_connection_cs: BoolProperty(name="Is Editing Connection CS", default=False) + ccs_x_angle: FloatProperty(name="Connection CS X Angle", update=updateConnectionCS) + ccs_y_angle: FloatProperty(name="Connection CS Y Angle", update=updateConnectionCS) + ccs_z_angle: FloatProperty(name="Connection CS Z Angle", update=updateConnectionCS) + ccs_empty: PointerProperty(name="CCS Empty", type=bpy.types.Object) diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index 4f33f899d1..0aa44036f8 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -1,3 +1,4 @@ +import bpy from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.structural.data import Data @@ -169,11 +170,63 @@ class BIM_PT_structural_member(Panel): if self.props.is_editing_axis: row = self.layout.row(align=True) row.prop(self.props, "axis_angle") - row.operator("bim.edit_structural_member_axis", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_structural_member_axis", text="", icon="CANCEL") + row.operator("bim.edit_structural_item_axis", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_structural_item_axis", text="", icon="CANCEL") else: row = self.layout.row() - row.operator("bim.enable_editing_structural_member_axis", text="Edit Axis", icon="GREASEPENCIL") + row.operator("bim.enable_editing_structural_item_axis", text="Edit Axis", icon="GREASEPENCIL") + else: + row = self.layout.row() + row.label(text="TODO") + + +class BIM_PT_structural_connection(Panel): + bl_label = "IFC Structural Connection" + bl_idname = "BIM_PT_structural_connection" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + @classmethod + def poll(cls, context): + if not context.active_object: + return False + props = context.active_object.BIMObjectProperties + if not props.ifc_definition_id: + return False + if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"): + return False + return True + + def draw(self, context): + self.oprops = context.active_object.BIMObjectProperties + self.props = context.active_object.BIMStructuralProperties + self.file = IfcStore.get_file() + + if self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcStructuralCurveConnection"): + if self.props.is_editing_axis: + row = self.layout.row(align=True) + row.prop(self.props, "axis_angle") + row.operator("bim.edit_structural_item_axis", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_structural_item_axis", text="", icon="CANCEL") + else: + row = self.layout.row() + row.operator("bim.enable_editing_structural_item_axis", text="Edit Axis", icon="GREASEPENCIL") + + elif self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcStructuralPointConnection"): + if self.props.is_editing_connection_cs: + row = self.layout.row(align=True) + row.label(text="Editing Connection CS") + row.operator("bim.edit_structural_item_connection_cs", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_structural_item_connection_cs", text="", icon="CANCEL") + row = self.layout.row(align=True) + row.prop(self.props, "ccs_x_angle") + row.prop(self.props, "ccs_y_angle") + row.prop(self.props, "ccs_z_angle") + else: + row = self.layout.row() + row.operator("bim.enable_editing_structural_item_connection_cs", text="Edit Connection CS", icon="GREASEPENCIL") else: row = self.layout.row() row.label(text="TODO") @@ -292,25 +345,76 @@ class BIM_PT_structural_load_cases(Panel): row.label(text=load_case["Name"] or "Unnamed", icon="CON_CLAMPTO") if self.props.active_load_case_id and self.props.active_load_case_id == load_case_id: - row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK") + if self.props.load_case_editing_type == "ATTRIBUTES": + row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK") + elif self.props.load_case_editing_type == "GROUPS": + row.operator("bim.add_structural_load_group", text="", icon="ADD").load_case = load_case_id row.operator("bim.disable_editing_structural_load_case", text="", icon="CANCEL") elif self.props.active_load_case_id: row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id else: + row.operator( + "bim.enable_editing_structural_load_case_groups", text="", icon="GHOST_ENABLED" + ).load_case = load_case_id row.operator( "bim.enable_editing_structural_load_case", text="", icon="GREASEPENCIL" ).load_case = load_case_id row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id if self.props.active_load_case_id == load_case_id: - self.draw_editable_load_case_ui() + if self.props.load_case_editing_type == "ATTRIBUTES": + self.draw_editable_load_case_ui() + elif self.props.load_case_editing_type == "GROUPS": + self.draw_editable_load_case_group_ui(load_case) def draw_editable_load_case_ui(self): for attribute in self.props.load_case_attributes: row = self.layout.row(align=True) if attribute.data_type == "string": row.prop(attribute, "string_value", text=attribute.name) + elif attribute.data_type == "float": + row.prop(attribute, "float_value", text=attribute.name) elif attribute.data_type == "enum": row.prop(attribute, "enum_value", text=attribute.name) if attribute.is_optional: row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + + def draw_editable_load_case_group_ui(self, load_case): + box = self.layout.box() + if not len(load_case["IsGroupedBy"]): + row = box.row(align=True) + row.label(text="No Load Groups Found") + for load_group_id in load_case["IsGroupedBy"]: + load_group = Data.load_groups[load_group_id] + row = box.row(align=True) + row.label(text=load_group["Name"] or "Unnamed", icon="GHOST_ENABLED") + op = row.operator("bim.enable_editing_structural_load_group_activities", text="", icon="GHOST_ENABLED") + op.load_group = load_group_id + row.operator("bim.remove_structural_load_group", text="", icon="X").load_group = load_group_id + + if self.props.active_load_group_id == load_group_id: + if self.props.load_group_editing_type == "ACTIVITY": + self.draw_editable_load_group_activities_ui(box, load_group) + + def draw_editable_load_group_activities_ui(self, layout, load_group): + row = layout.row(align=True) + row.prop(self.props, "applicable_structural_activity_types", text="") + op = row.operator("bim.add_structural_activity", text="", icon="ADD") + op.load_group = load_group["id"] + layout.template_list( + "BIM_UL_structural_activities", + "", + self.props, + "load_group_activities", + self.props, + "active_load_group_activity_index", + ) + + + +class BIM_UL_structural_activities(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + row.label(text=item.applied_load_class) diff --git a/src/ifcgeom/IfcGeomTree.h b/src/ifcgeom/IfcGeomTree.h index 049066261b..40f02e8ce7 100644 --- a/src/ifcgeom/IfcGeomTree.h +++ b/src/ifcgeom/IfcGeomTree.h @@ -166,8 +166,11 @@ namespace IfcGeom { std::vector ts; - if (IfcGeom::Kernel::count(s, TopAbs_SHELL) == 0) { - return ts; + if (extend < 0.) { + // Shell are only required when we do the boolean based intersection check + if (IfcGeom::Kernel::count(s, TopAbs_SHELL) == 0) { + return ts; + } } ts = select_box(bb, completely_within); @@ -288,7 +291,9 @@ namespace IfcGeom { if (it.initialize()) { do { IfcGeom::BRepElement* elem = (IfcGeom::BRepElement*)it.get(); - add((IfcUtil::IfcBaseEntity*)it.file()->instance_by_id(elem->id()), elem->geometry().as_compound()); + auto compound = elem->geometry().as_compound(); + compound.Move(elem->transformation().data()); + add((IfcUtil::IfcBaseEntity*)it.file()->instance_by_id(elem->id()), compound); } while (it.next()); } }