This commit is contained in:
admin
2021-05-10 14:06:18 +08:00
parent db5432d902
commit 58e9f1baf9
13 changed files with 593 additions and 489 deletions
+56 -145
View File
@@ -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
+1 -1
View File
@@ -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)
@@ -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(
@@ -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"}
@@ -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():
@@ -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",
@@ -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="[]")
@@ -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")
@@ -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,
)
@@ -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"}
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"}
@@ -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)
@@ -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)
+8 -3
View File
@@ -166,8 +166,11 @@ namespace IfcGeom {
std::vector<T> 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<double>* elem = (IfcGeom::BRepElement<double>*)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());
}
}