Merge branch 'GSoC#45-IDS-checking' of https://github.com/atomczak/IfcOpenShell into GSoC#45-IDS-checking

This commit is contained in:
atom3
2021-04-29 21:33:51 +02:00
77 changed files with 2865 additions and 337 deletions
+2 -1
View File
@@ -24,8 +24,9 @@ if bpy is not None:
"aggregate": None, "aggregate": None,
"geometry": None, "geometry": None,
"cobie": None, "cobie": None,
"sequence": None, "resource": None,
"cost": None, "cost": None,
"sequence": None,
"group": None, "group": None,
"structural": None, "structural": None,
"material": None, "material": None,
Binary file not shown.
+3
View File
@@ -6,6 +6,7 @@ import ifcopenshell.api.owner.settings
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data as AttributeData from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.type.data import Data as TypeData
def mode_callback(obj, data): def mode_callback(obj, data):
@@ -33,6 +34,8 @@ def name_callback(obj, data):
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy): if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
collection = obj.users_collection[0] collection = obj.users_collection[0]
collection.name = obj.name collection.name = obj.name
if element.is_a("IfcTypeProduct"):
TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:]) element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
@@ -6,11 +6,19 @@ classes = (
operator.RemoveCostSchedule, operator.RemoveCostSchedule,
operator.EditCostSchedule, operator.EditCostSchedule,
operator.EditCostItem, operator.EditCostItem,
operator.EditCostItemQuantity,
operator.EditCostItemValue,
operator.EnableEditingCostSchedule, operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems, operator.EnableEditingCostItems,
operator.EnableEditingCostItem, operator.EnableEditingCostItem,
operator.EnableEditingCostItemQuantities,
operator.EnableEditingCostItemQuantity,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostItemValue,
operator.DisableEditingCostItem, operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule, operator.DisableEditingCostSchedule,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.AddCostItem, operator.AddCostItem,
operator.AddSummaryCostItem, operator.AddSummaryCostItem,
operator.ExpandCostItem, operator.ExpandCostItem,
@@ -18,6 +26,10 @@ classes = (
operator.RemoveCostItem, operator.RemoveCostItem,
operator.AssignControl, operator.AssignControl,
operator.UnassignControl, operator.UnassignControl,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostItemValue,
operator.RemoveCostItemValue,
prop.CostItem, prop.CostItem,
prop.BIMCostProperties, prop.BIMCostProperties,
ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_schedules,
@@ -1,3 +1,4 @@
import os
import bpy import bpy
import json import json
import ifcopenshell.api import ifcopenshell.api
@@ -98,13 +99,29 @@ class EnableEditingCostItems(bpy.types.Operator):
cost_schedule: bpy.props.IntProperty() cost_schedule: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound:
# lol
# TODO: make pitch higher as costs rise
try:
import aud
device = aud.Device()
# chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
handle = device.play(sound)
sound_buffered = aud.Sound.buffer(sound)
handle_buffered = device.play(sound_buffered)
handle.stop()
handle_buffered.stop()
except:
pass # ah well
self.props = context.scene.BIMCostProperties self.props = context.scene.BIMCostProperties
self.props.active_cost_schedule_id = self.cost_schedule self.props.active_cost_schedule_id = self.cost_schedule
while len(self.props.cost_items) > 0: while len(self.props.cost_items) > 0:
self.props.cost_items.remove(0) self.props.cost_items.remove(0)
self.contracted_cost_items = json.loads(self.props.contracted_cost_items) self.contracted_cost_items = json.loads(self.props.contracted_cost_items)
for related_object_id in Data.cost_schedules[self.cost_schedule]["RelatedObjects"]: for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.create_new_cost_item_li(related_object_id, 0) self.create_new_cost_item_li(related_object_id, 0)
self.props.is_editing = "COST_ITEMS" self.props.is_editing = "COST_ITEMS"
return {"FINISHED"} return {"FINISHED"}
@@ -116,10 +133,10 @@ class EnableEditingCostItems(bpy.types.Operator):
new.name = cost_item["Name"] or "Unnamed" new.name = cost_item["Name"] or "Unnamed"
new.is_expanded = related_object_id not in self.contracted_cost_items new.is_expanded = related_object_id not in self.contracted_cost_items
new.level_index = level_index new.level_index = level_index
if cost_item["RelatedObjects"]: if cost_item["IsNestedBy"]:
new.has_children = True new.has_children = True
if new.is_expanded: if new.is_expanded:
for related_object_id in cost_item["RelatedObjects"]: for related_object_id in cost_item["IsNestedBy"]:
self.create_new_cost_item_li(related_object_id, level_index + 1) self.create_new_cost_item_li(related_object_id, level_index + 1)
return {"FINISHED"} return {"FINISHED"}
@@ -242,6 +259,7 @@ class EnableEditingCostItem(bpy.types.Operator):
if data[attribute.name()]: if data[attribute.name()]:
new.enum_value = data[attribute.name()] new.enum_value = data[attribute.name()]
props.active_cost_item_id = self.cost_item props.active_cost_item_id = self.cost_item
props.cost_item_editing_type = "ATTRIBUTES"
return {"FINISHED"} return {"FINISHED"}
@@ -327,3 +345,264 @@ class UnassignControl(bpy.types.Operator):
) )
Data.load(self.file) Data.load(self.file)
return {"FINISHED"} return {"FINISHED"}
class EnableEditingCostItemQuantities(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_quantities"
bl_label = "Enable Editing Cost Item Quantities"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
props.active_cost_item_id = self.cost_item
props.cost_item_editing_type = "QUANTITIES"
return {"FINISHED"}
class EnableEditingCostItemValues(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_values"
bl_label = "Enable Editing Cost Item Values"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
props.active_cost_item_id = self.cost_item
props.cost_item_editing_type = "VALUES"
return {"FINISHED"}
class AddCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.add_cost_item_quantity"
bl_label = "Add Cost Item Quantity"
cost_item: bpy.props.IntProperty()
ifc_class: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
self.props = context.scene.BIMCostProperties
if self.props.quantity_types == "QTO":
self.add_quantities_from_qto_filter()
else:
self.add_manual_quantity()
Data.load(self.file)
return {"FINISHED"}
def add_quantities_from_qto_filter(self):
ifcopenshell.api.run(
"cost.assign_cost_item_product_quantities",
self.file,
cost_item=self.file.by_id(self.cost_item),
qto_name=self.props.qto_name,
prop_name=self.props.prop_name
)
def add_manual_quantity(self):
ifcopenshell.api.run(
"cost.add_cost_item_quantity",
self.file,
cost_item=self.file.by_id(self.cost_item),
ifc_class=self.ifc_class,
)
class RemoveCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.remove_cost_item_quantity"
bl_label = "Add Cost Item Quantity"
cost_item: bpy.props.IntProperty()
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.remove_cost_item_quantity",
self.file,
cost_item=self.file.by_id(self.cost_item),
physical_quantity=self.file.by_id(self.physical_quantity),
)
Data.load(self.file)
return {"FINISHED"}
class EnableEditingCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_quantity"
bl_label = "Enable Editing Cost Item Quantity"
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
while len(self.props.quantity_attributes) > 0:
self.props.quantity_attributes.remove(0)
self.props.active_cost_item_quantity_id = self.physical_quantity
data = Data.physical_quantities[self.physical_quantity]
for attribute in IfcStore.get_schema().declaration_by_name(data["type"]).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = self.props.quantity_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 == "float":
new.float_value = 0.0 if new.is_null else data[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else data[attribute.name()]
return {"FINISHED"}
class DisableEditingCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item_quantity"
bl_label = "Disable Editing Cost Item Quantity"
def execute(self, context):
props = context.scene.BIMCostProperties
props.active_cost_item_quantity_id = 0
return {"FINISHED"}
class EditCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.edit_cost_item_quantity"
bl_label = "Edit Cost Item Quantity"
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
attributes = {}
for attribute in props.quantity_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
if attribute.data_type == "float":
attributes[attribute.name] = attribute.float_value
if attribute.data_type == "integer":
attributes[attribute.name] = attribute.int_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_item_quantity",
self.file,
**{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_cost_item_quantity()
return {"FINISHED"}
class AddCostItemValue(bpy.types.Operator):
bl_idname = "bim.add_cost_item_value"
bl_label = "Add Cost Item Value"
cost_item: bpy.props.IntProperty()
cost_type: bpy.props.StringProperty()
cost_category: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
if self.cost_type == "FIXED":
category = None
elif self.cost_type == "SUM":
category = "*"
elif self.cost_type == "CATEGORY":
category = self.cost_category
value = ifcopenshell.api.run("cost.add_cost_item_value", self.file, cost_item=self.file.by_id(self.cost_item))
ifcopenshell.api.run(
"cost.edit_cost_item_value", self.file, cost_value=value, attributes={"Category": category}
)
Data.load(self.file)
return {"FINISHED"}
class RemoveCostItemValue(bpy.types.Operator):
bl_idname = "bim.remove_cost_item_value"
bl_label = "Add Cost Item Value"
cost_value: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.remove_cost_item_value",
self.file,
cost_value=self.file.by_id(self.cost_value),
)
Data.load(self.file)
return {"FINISHED"}
class EnableEditingCostItemValue(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_value"
bl_label = "Enable Editing Cost Item Value"
cost_value: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
while len(self.props.cost_value_attributes) > 0:
self.props.cost_value_attributes.remove(0)
self.props.active_cost_item_value_id = self.cost_value
data = Data.cost_values[self.cost_value]
for attribute in IfcStore.get_schema().declaration_by_name(data["type"]).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or isinstance(data_type, tuple):
continue
new = self.props.cost_value_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() == "AppliedValue":
# TODO: for now, only support simple values
new.data_type = "float"
new.float_value = 0.0 if new.is_null else data[attribute.name()]
if 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 == "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()]
return {"FINISHED"}
class DisableEditingCostItemValue(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item_value"
bl_label = "Disable Editing Cost Item Value"
def execute(self, context):
props = context.scene.BIMCostProperties
props.active_cost_item_value_id = 0
return {"FINISHED"}
class EditCostItemValue(bpy.types.Operator):
bl_idname = "bim.edit_cost_item_value"
bl_label = "Edit Cost Item Value"
cost_value: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
attributes = {}
for attribute in props.cost_value_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
if attribute.data_type == "float":
attributes[attribute.name] = attribute.float_value
if attribute.data_type == "integer":
attributes[attribute.name] = attribute.int_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_item_value",
self.file,
**{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_cost_item_value()
return {"FINISHED"}
@@ -16,6 +16,28 @@ from bpy.props import (
) )
quantitytypes_enum = []
def purge():
global quantitytypes_enum
quantitytypes_enum = []
def getQuantityTypes(self, context):
global quantitytypes_enum
if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
quantitytypes_enum.clear()
quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")]
quantitytypes_enum.extend(
[
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
]
)
return quantitytypes_enum
def updateCostItemName(self, context): def updateCostItemName(self, context):
if self.name == "Unnamed": if self.name == "Unnamed":
return return
@@ -46,6 +68,23 @@ class BIMCostProperties(PropertyGroup):
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id") active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
cost_items: CollectionProperty(name="Work Calendar", type=CostItem) cost_items: CollectionProperty(name="Work Calendar", type=CostItem)
active_cost_item_id: IntProperty(name="Active Cost Id") active_cost_item_id: IntProperty(name="Active Cost Id")
cost_item_editing_type: StringProperty(name="Cost Item Editing Type")
active_cost_item_index: IntProperty(name="Active Cost Item Index") active_cost_item_index: IntProperty(name="Active Cost Item Index")
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute) cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]") contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types")
qto_name: StringProperty(name="Qto Name")
prop_name: StringProperty(name="Prop Name")
active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id")
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
cost_types: EnumProperty(
items=[
("FIXED", "Fixed", "The cost value is a fixed number"),
("SUM", "Sum", "The cost value is automatically derived from the sum of all nested cost items"),
("CATEGORY", "Category", "The cost value represents a single category"),
],
name="Cost Types",
)
cost_category: StringProperty(name="Cost Category")
active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id")
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
+118 -2
View File
@@ -72,7 +72,12 @@ class BIM_PT_cost_schedules(Panel):
"active_cost_item_index", "active_cost_item_index",
) )
if self.props.active_cost_item_id: if self.props.active_cost_item_id:
self.draw_editable_cost_item_attributes_ui() if self.props.cost_item_editing_type == "ATTRIBUTES":
self.draw_editable_cost_item_attributes_ui()
elif self.props.cost_item_editing_type == "QUANTITIES":
self.draw_editable_cost_item_quantities_ui()
elif self.props.cost_item_editing_type == "VALUES":
self.draw_editable_cost_item_values_ui()
def draw_editable_cost_item_attributes_ui(self): def draw_editable_cost_item_attributes_ui(self):
for attribute in self.props.cost_item_attributes: for attribute in self.props.cost_item_attributes:
@@ -88,11 +93,112 @@ class BIM_PT_cost_schedules(Panel):
if attribute.is_optional: if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "quantity_types", text="")
if self.props.quantity_types == "QTO":
row.prop(self.props, "qto_name", text="")
row.prop(self.props, "prop_name", text="")
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id
op.ifc_class = self.props.quantity_types
for quantity_id in Data.cost_items[self.props.active_cost_item_id]["CostQuantities"]:
quantity = Data.physical_quantities[quantity_id]
value = quantity[[k for k in quantity.keys() if "Value" in k][0]]
row = self.layout.row(align=True)
row.label(text=quantity["Name"])
row.label(text=str(value))
if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id:
op = row.operator("bim.edit_cost_item_quantity", text="", icon="CHECKMARK")
op.physical_quantity = quantity_id
row.operator("bim.disable_editing_cost_item_quantity", text="", icon="CANCEL")
elif self.props.active_cost_item_quantity_id:
op = row.operator("bim.remove_cost_item_quantity", text="", icon="X")
op.cost_item = self.props.active_cost_item_id
op.physical_quantity = quantity_id
else:
op = row.operator("bim.enable_editing_cost_item_quantity", text="", icon="GREASEPENCIL")
op.physical_quantity = quantity_id
op = row.operator("bim.remove_cost_item_quantity", text="", icon="X")
op.cost_item = self.props.active_cost_item_id
op.physical_quantity = quantity_id
if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id:
box = self.layout.box()
self.draw_editable_cost_item_quantity_ui(box)
def draw_editable_cost_item_quantity_ui(self, layout):
for attribute in self.props.quantity_attributes:
row = layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_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_cost_item_values_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "cost_types", text="")
if self.props.cost_types == "CATEGORY":
row.prop(self.props, "cost_category", text="")
op = row.operator("bim.add_cost_item_value", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id
op.cost_type = self.props.cost_types
if self.props.cost_types == "CATEGORY":
op.cost_category = self.props.cost_category
for cost_value_id in Data.cost_items[self.props.active_cost_item_id]["CostValues"]:
cost_value = Data.cost_values[cost_value_id]
row = self.layout.row(align=True)
row.label(text=str(cost_value["Category"]))
row.label(text=str(cost_value["AppliedValue"]))
if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id:
op = row.operator("bim.edit_cost_item_value", text="", icon="CHECKMARK")
op.cost_value = cost_value_id
row.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
elif self.props.active_cost_item_value_id:
op = row.operator("bim.remove_cost_item_value", text="", icon="X")
op.cost_value = cost_value_id
else:
op = row.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
op.cost_value = cost_value_id
op = row.operator("bim.remove_cost_item_value", text="", icon="X")
op.cost_value = cost_value_id
if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id:
box = self.layout.box()
self.draw_editable_cost_item_value_ui(box)
def draw_editable_cost_item_value_ui(self, layout):
for attribute in self.props.cost_value_attributes:
row = layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_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="")
class BIM_UL_cost_items(UIList): class BIM_UL_cost_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
props = context.scene.BIMCostProperties props = context.scene.BIMCostProperties
cost_item = Data.cost_items[item.ifc_definition_id]
row = layout.row(align=True) row = layout.row(align=True)
for i in range(0, item.level_index): for i in range(0, item.level_index):
row.label(text="", icon="BLANK1") row.label(text="", icon="BLANK1")
@@ -109,10 +215,20 @@ class BIM_UL_cost_items(UIList):
row.label(text="", icon="DOT") row.label(text="", icon="DOT")
row.prop(item, "name", emboss=False, text="") row.prop(item, "name", emboss=False, text="")
row.label(text="M3")
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
op.cost_item = item.ifc_definition_id
row.label(text=str(cost_item["TotalCostQuantity"]))
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = item.ifc_definition_id
row.label(text=str(cost_item["TotalAppliedValue"]))
row.label(text=str(cost_item["TotalCostValue"]), icon="CON_TRANSLIKE")
if context.active_object: if context.active_object:
oprops = context.active_object.BIMObjectProperties oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True) row = layout.row(align=True)
if oprops.ifc_definition_id in Data.cost_items[item.ifc_definition_id]["Controls"]: if oprops.ifc_definition_id in cost_item["Controls"]:
op = row.operator("bim.unassign_control", text="", icon="KEYFRAME_HLT", emboss=False) op = row.operator("bim.unassign_control", text="", icon="KEYFRAME_HLT", emboss=False)
op.cost_item = item.ifc_definition_id op.cost_item = item.ifc_definition_id
else: else:
@@ -0,0 +1,31 @@
import bpy
from . import ui, prop, operator
classes = (
operator.DisableResourceEditingUI,
operator.DisableEditingResource,
operator.EnableEditingResource,
operator.LoadResources,
operator.AddResource,
operator.EditResource,
operator.RemoveResource,
operator.LoadResourceProperties,
operator.ExpandResource,
operator.ContractResource,
operator.AssignResource,
operator.UnassignResource,
prop.Resource,
prop.BIMResourceProperties,
prop.BIMResourceTreeProperties,
ui.BIM_PT_resources,
ui.BIM_UL_resources,
)
def register():
bpy.types.Scene.BIMResourceProperties = bpy.props.PointerProperty(type=prop.BIMResourceProperties)
bpy.types.Scene.BIMResourceTreeProperties = bpy.props.PointerProperty(type=prop.BIMResourceTreeProperties)
def unregister():
del bpy.types.Scene.BIMResourceProperties
del bpy.types.Scene.BIMResourceTreeProperties
@@ -0,0 +1,243 @@
import bpy
import json
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
class LoadResources(bpy.types.Operator):
bl_idname = "bim.load_resources"
bl_label = "Load Resources"
def execute(self, context):
self.props = context.scene.BIMResourceProperties
self.tprops = context.scene.BIMResourceTreeProperties
while len(self.tprops.resources) > 0:
self.tprops.resources.remove(0)
self.contracted_resources = json.loads(self.props.contracted_resources)
for resource_id, data in Data.resources.items():
if not data["HasContext"]:
continue
self.create_new_resource_li(resource_id, 0)
bpy.ops.bim.load_resource_properties()
self.props.is_editing = True
return {"FINISHED"}
def create_new_resource_li(self, related_object_id, level_index):
resource = Data.resources[related_object_id]
new = self.tprops.resources.add()
new.ifc_definition_id = related_object_id
new.is_expanded = related_object_id not in self.contracted_resources
new.level_index = level_index
if resource["IsNestedBy"]:
new.has_children = True
if new.is_expanded:
for related_object_id in resource["IsNestedBy"]:
self.create_new_resource_li(related_object_id, level_index + 1)
return {"FINISHED"}
class EnableEditingResource(bpy.types.Operator):
bl_idname = "bim.enable_editing_resource"
bl_label = "Enable Editing Resource"
resource: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMResourceProperties
self.props.active_resource_id = self.resource
while len(self.props.resource_attributes) > 0:
self.props.resource_attributes.remove(0)
self.enable_editing_resource()
return {"FINISHED"}
def enable_editing_resource(self):
data = Data.resources[self.resource]
for attribute in IfcStore.get_schema().declaration_by_name(data["type"]).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or isinstance(data_type, tuple):
continue
new = self.props.resource_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()]
class LoadResourceProperties(bpy.types.Operator):
bl_idname = "bim.load_resource_properties"
bl_label = "Load Resource Properties"
resource: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMResourceProperties
self.tprops = context.scene.BIMResourceTreeProperties
self.props.is_resource_update_enabled = False
for item in self.tprops.resources:
if self.resource and item.ifc_definition_id != self.resource:
continue
resource = Data.resources[item.ifc_definition_id]
item.name = resource["Name"] or "Unnamed"
self.props.is_resource_update_enabled = True
return {"FINISHED"}
class DisableEditingResource(bpy.types.Operator):
bl_idname = "bim.disable_editing_resource"
bl_label = "Disable Editing Workplan"
def execute(self, context):
context.scene.BIMResourceProperties.active_resource_id = 0
return {"FINISHED"}
class DisableResourceEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_resource_editing_ui"
bl_label = "Disable Resources Editing UI"
def execute(self, context):
context.scene.BIMResourceProperties.is_editing = False
return {"FINISHED"}
class AddResource(bpy.types.Operator):
bl_idname = "bim.add_resource"
bl_label = "Add resource"
ifc_class: bpy.props.StringProperty()
resource: bpy.props.IntProperty()
def execute(self, context):
ifcopenshell.api.run(
"resource.add_resource",
IfcStore.get_file(),
parent_resource=IfcStore.get_file().by_id(self.resource) if self.resource else None,
ifc_class=self.ifc_class,
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_resources()
return {"FINISHED"}
class EditResource(bpy.types.Operator):
bl_idname = "bim.edit_resource"
bl_label = "Edit Resource"
def execute(self, context):
props = context.scene.BIMResourceProperties
attributes = {}
for attribute in props.resource_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
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"resource.edit_resource",
self.file,
**{"resource": self.file.by_id(props.active_resource_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_resource_properties(resource=props.active_resource_id)
bpy.ops.bim.disable_editing_resource()
return {"FINISHED"}
class RemoveResource(bpy.types.Operator):
bl_idname = "bim.remove_resource"
bl_label = "Remove Resource"
resource: bpy.props.IntProperty()
def execute(self, context):
ifcopenshell.api.run(
"resource.remove_resource",
IfcStore.get_file(),
resource=IfcStore.get_file().by_id(self.resource),
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_resources()
return {"FINISHED"}
class ExpandResource(bpy.types.Operator):
bl_idname = "bim.expand_resource"
bl_label = "Expand Resource"
resource: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMResourceProperties
self.file = IfcStore.get_file()
contracted_resources = json.loads(props.contracted_resources)
contracted_resources.remove(self.resource)
props.contracted_resources = json.dumps(contracted_resources)
Data.load(self.file)
bpy.ops.bim.load_resources()
return {"FINISHED"}
class ContractResource(bpy.types.Operator):
bl_idname = "bim.contract_resource"
bl_label = "Contract Resource"
resource: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMResourceProperties
self.file = IfcStore.get_file()
contracted_resources = json.loads(props.contracted_resources)
contracted_resources.append(self.resource)
props.contracted_resources = json.dumps(contracted_resources)
Data.load(self.file)
bpy.ops.bim.load_resources()
return {"FINISHED"}
class AssignResource(bpy.types.Operator):
bl_idname = "bim.assign_resource"
bl_label = "Assign Resource"
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"resource.assign_resource",
self.file,
relating_resource=self.file.by_id(self.resource),
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
)
Data.load(self.file)
return {"FINISHED"}
class UnassignResource(bpy.types.Operator):
bl_idname = "bim.unassign_resource"
bl_label = "Unassign Resource"
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"resource.unassign_resource",
self.file,
relating_resource=self.file.by_id(self.resource),
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
)
Data.load(self.file)
return {"FINISHED"}
@@ -0,0 +1,54 @@
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def updateResourceName(self, context):
props = context.scene.BIMResourceProperties
if not props.is_resource_update_enabled or self.name == "Unnamed":
return
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"resource.edit_resource",
self.file,
**{"resource": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
)
Data.load(IfcStore.get_file())
if props.active_resource_id == self.ifc_definition_id:
attribute = props.resource_attributes.get("Name")
attribute.string_value = self.name
class Resource(PropertyGroup):
name: StringProperty(name="Name", update=updateResourceName)
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
class BIMResourceTreeProperties(PropertyGroup):
resources: CollectionProperty(name="Resources", type=Resource)
class BIMResourceProperties(PropertyGroup):
resource_attributes: CollectionProperty(name="Resource Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing")
active_resource_index: IntProperty(name="Active Resource Index")
active_resource_id: IntProperty(name="Active Resource Id")
contracted_resources: StringProperty(name="Contracted Resources", default="[]")
is_resource_update_enabled: BoolProperty(name="Is Resource Update Enabled", default=True)
is_loaded: BoolProperty(name="Is Editing")
@@ -0,0 +1,137 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
class BIM_PT_resources(Panel):
bl_label = "IFC Resources"
bl_idname = "BIM_PT_resources"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
self.props = context.scene.BIMResourceProperties
self.tprops = context.scene.BIMResourceTreeProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
row = self.layout.row(align=True)
row.label(text=f"{len(Data.resources)} Resources Found")
if self.props.is_editing:
row.operator("bim.disable_resource_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_resources", text="", icon="GREASEPENCIL")
if not self.props.is_editing:
return
row = self.layout.row(align=True)
op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT")
op.ifc_class = "IfcSubContractResource"
op.resource = 0
op = row.operator("bim.add_resource", text="Add Crew", icon="COMMUNITY")
op.ifc_class = "IfcCrewResource"
op.resource = 0
icon_map = {
"IfcConstructionEquipmentResource": "TOOL_SETTINGS",
"IfcLaborResource": "OUTLINER_OB_ARMATURE",
"IfcConstructionMaterialResource": "MATERIAL",
"IfcConstructionProductResource": "PACKAGE",
}
total_resources = len(self.tprops.resources)
if total_resources and self.props.active_resource_index < total_resources:
row = self.layout.row(align=True)
for ifc_class, icon in icon_map.items():
label = ifc_class.replace("Ifc", "").replace("Construction", "").replace("Resource", "")
op = row.operator("bim.add_resource", text=label, icon=icon)
op.resource = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
op.ifc_class = ifc_class
self.layout.template_list(
"BIM_UL_resources",
"",
self.tprops,
"resources",
self.props,
"active_resource_index",
)
if self.props.active_resource_id:
self.draw_editable_resource_ui()
def draw_editable_resource_ui(self):
for attribute in self.props.resource_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_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="")
class BIM_UL_resources(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
resource = Data.resources[item.ifc_definition_id]
icon_map = {
"IfcSubContractResource": "TEXT",
"IfcCrewResource": "COMMUNITY",
"IfcConstructionEquipmentResource": "TOOL_SETTINGS",
"IfcLaborResource": "OUTLINER_OB_ARMATURE",
"IfcConstructionMaterialResource": "MATERIAL",
"IfcConstructionProductResource": "PACKAGE",
}
if item:
props = context.scene.BIMResourceProperties
row = layout.row(align=True)
for i in range(0, item.level_index):
row.label(text="", icon="BLANK1")
if item.has_children:
if item.is_expanded:
row.operator(
"bim.contract_resource", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).resource = item.ifc_definition_id
else:
row.operator(
"bim.expand_resource", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).resource = item.ifc_definition_id
else:
row.label(text="", icon="DOT")
row.prop(item, "name", emboss=False, text="", icon=icon_map[resource["type"]])
if context.active_object:
oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True)
if oprops.ifc_definition_id in Data.resources[item.ifc_definition_id]["ResourceOf"]:
op = row.operator("bim.unassign_resource", text="", icon="KEYFRAME_HLT", emboss=False)
op.resource = item.ifc_definition_id
else:
op = row.operator("bim.assign_resource", text="", icon="KEYFRAME", emboss=False)
op.resource = item.ifc_definition_id
if props.active_resource_id == item.ifc_definition_id:
row.operator("bim.edit_resource", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
elif props.active_resource_id:
row.operator("bim.add_resource", text="", icon="ADD").resource = item.ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id
else:
row.operator(
"bim.enable_editing_resource", text="", icon="GREASEPENCIL"
).resource = item.ifc_definition_id
row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id
@@ -76,6 +76,7 @@ class ReassignClass(bpy.types.Operator):
class AssignClass(bpy.types.Operator): class AssignClass(bpy.types.Operator):
bl_idname = "bim.assign_class" bl_idname = "bim.assign_class"
bl_label = "Assign IFC Class" bl_label = "Assign IFC Class"
bl_options = {'REGISTER', 'UNDO'}
obj: bpy.props.StringProperty() obj: bpy.props.StringProperty()
ifc_class: bpy.props.StringProperty() ifc_class: bpy.props.StringProperty()
predefined_type: bpy.props.StringProperty() predefined_type: bpy.props.StringProperty()
@@ -2,13 +2,14 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.LoadWorkPlans,
operator.DisableWorkPlanEditingUI,
operator.AddWorkPlan, operator.AddWorkPlan,
operator.EditWorkPlan, operator.EditWorkPlan,
operator.RemoveWorkPlan, operator.RemoveWorkPlan,
operator.EnableEditingWorkPlan, operator.EnableEditingWorkPlan,
operator.DisableEditingWorkPlan, operator.DisableEditingWorkPlan,
operator.EnableEditingWorkPlanSchedules,
operator.AssignWorkSchedule,
operator.UnassignWorkSchedule,
operator.AddWorkSchedule, operator.AddWorkSchedule,
operator.EditWorkSchedule, operator.EditWorkSchedule,
operator.RemoveWorkSchedule, operator.RemoveWorkSchedule,
@@ -16,13 +17,21 @@ classes = (
operator.EnableEditingTasks, operator.EnableEditingTasks,
operator.DisableEditingWorkSchedule, operator.DisableEditingWorkSchedule,
operator.DisableTaskEditingUI, operator.DisableTaskEditingUI,
operator.LoadWorkCalendars,
operator.DisableWorkCalendarEditingUI,
operator.AddWorkCalendar, operator.AddWorkCalendar,
operator.EditWorkCalendar, operator.EditWorkCalendar,
operator.EditWorkTime,
operator.RemoveWorkCalendar, operator.RemoveWorkCalendar,
operator.RemoveWorkTime,
operator.UnassignRecurrencePattern,
operator.RemoveTimePeriod,
operator.EnableEditingWorkCalendar, operator.EnableEditingWorkCalendar,
operator.EnableEditingWorkTime,
operator.EnableEditingWorkCalendarTimes,
operator.DisableEditingWorkCalendar, operator.DisableEditingWorkCalendar,
operator.DisableEditingWorkTime,
operator.AddWorkTime,
operator.AssignRecurrencePattern,
operator.AddTimePeriod,
operator.AddTask, operator.AddTask,
operator.AddSummaryTask, operator.AddSummaryTask,
operator.ExpandTask, operator.ExpandTask,
@@ -49,12 +58,11 @@ classes = (
prop.BIMWorkScheduleProperties, prop.BIMWorkScheduleProperties,
prop.BIMTaskTreeProperties, prop.BIMTaskTreeProperties,
prop.WorkCalendar, prop.WorkCalendar,
prop.RecurrenceComponent,
prop.BIMWorkCalendarProperties, prop.BIMWorkCalendarProperties,
ui.BIM_PT_work_plans, ui.BIM_PT_work_plans,
ui.BIM_UL_work_plans,
ui.BIM_PT_work_schedules, ui.BIM_PT_work_schedules,
ui.BIM_PT_work_calendars, ui.BIM_PT_work_calendars,
ui.BIM_UL_work_calendars,
ui.BIM_UL_tasks, ui.BIM_UL_tasks,
) )
@@ -5,6 +5,7 @@ import time
import pystache import pystache
import webbrowser import webbrowser
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.date
from datetime import datetime from datetime import datetime
from dateutil import parser from dateutil import parser
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -12,32 +13,6 @@ from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.sequence.data import Data from ifcopenshell.api.sequence.data import Data
class LoadWorkPlans(bpy.types.Operator):
bl_idname = "bim.load_work_plans"
bl_label = "Load Work Plans"
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
while len(props.work_plans) > 0:
props.work_plans.remove(0)
for ifc_definition_id, work_plan in Data.work_plans.items():
new = props.work_plans.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_plan["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_plan()
return {"FINISHED"}
class DisableWorkPlanEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_plan_editing_ui"
bl_label = "Disable WorkPlan Editing UI"
def execute(self, context):
context.scene.BIMWorkPlanProperties.is_editing = False
return {"FINISHED"}
class AddWorkPlan(bpy.types.Operator): class AddWorkPlan(bpy.types.Operator):
bl_idname = "bim.add_work_plan" bl_idname = "bim.add_work_plan"
bl_label = "Add Work Plan" bl_label = "Add Work Plan"
@@ -45,7 +20,6 @@ class AddWorkPlan(bpy.types.Operator):
def execute(self, context): def execute(self, context):
ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file()) ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file())
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
return {"FINISHED"} return {"FINISHED"}
@@ -71,7 +45,7 @@ class EditWorkPlan(bpy.types.Operator):
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes}, **{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes},
) )
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans() bpy.ops.bim.disable_editing_work_plan()
return {"FINISHED"} return {"FINISHED"}
@@ -84,7 +58,6 @@ class RemoveWorkPlan(bpy.types.Operator):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)}) ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)})
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
return {"FINISHED"} return {"FINISHED"}
@@ -118,6 +91,7 @@ class EnableEditingWorkPlan(bpy.types.Operator):
if data[attribute.name()]: if data[attribute.name()]:
new.enum_value = data[attribute.name()] new.enum_value = data[attribute.name()]
props.active_work_plan_id = self.work_plan props.active_work_plan_id = self.work_plan
props.is_editing = "ATTRIBUTES"
return {"FINISHED"} return {"FINISHED"}
@@ -130,6 +104,58 @@ class DisableEditingWorkPlan(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class EnableEditingWorkPlanSchedules(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_plan_schedules"
bl_label = "Enable Editing Work Plan Schedules"
work_plan: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
props.active_work_plan_id = self.work_plan
props.is_editing = "SCHEDULES"
return {"FINISHED"}
class AssignWorkSchedule(bpy.types.Operator):
bl_idname = "bim.assign_work_schedule"
bl_label = "Assign Work Schedule"
work_plan: bpy.props.IntProperty()
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
"product": self.file.by_id(self.work_schedule),
},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnassignWorkSchedule(bpy.types.Operator):
bl_idname = "bim.unassign_work_schedule"
bl_label = "Unassign Work Schedule"
work_plan: bpy.props.IntProperty()
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"aggregate.unassign_object",
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
"product": self.file.by_id(self.work_schedule),
},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class AddWorkSchedule(bpy.types.Operator): class AddWorkSchedule(bpy.types.Operator):
bl_idname = "bim.add_work_schedule" bl_idname = "bim.add_work_schedule"
bl_label = "Add Work Schedule" bl_label = "Add Work Schedule"
@@ -722,32 +748,6 @@ class GenerateGanttChart(bpy.types.Operator):
self.create_new_task_json(task_id) self.create_new_task_json(task_id)
class LoadWorkCalendars(bpy.types.Operator):
bl_idname = "bim.load_work_calendars"
bl_label = "Load Work Calendars"
def execute(self, context):
props = context.scene.BIMWorkCalendarProperties
while len(props.work_calendars) > 0:
props.work_calendars.remove(0)
for ifc_definition_id, work_calendar in Data.work_calendars.items():
new = props.work_calendars.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_calendar["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_calendar()
return {"FINISHED"}
class DisableWorkCalendarEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_calendar_editing_ui"
bl_label = "Disable WorkCalendar Editing UI"
def execute(self, context):
context.scene.BIMWorkCalendarProperties.is_editing = False
return {"FINISHED"}
class AddWorkCalendar(bpy.types.Operator): class AddWorkCalendar(bpy.types.Operator):
bl_idname = "bim.add_work_calendar" bl_idname = "bim.add_work_calendar"
bl_label = "Add Work Calendar" bl_label = "Add Work Calendar"
@@ -755,7 +755,6 @@ class AddWorkCalendar(bpy.types.Operator):
def execute(self, context): def execute(self, context):
ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file()) ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file())
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_calendars()
return {"FINISHED"} return {"FINISHED"}
@@ -781,7 +780,7 @@ class EditWorkCalendar(bpy.types.Operator):
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes}, **{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes},
) )
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_calendars() bpy.ops.bim.disable_editing_work_calendar()
return {"FINISHED"} return {"FINISHED"}
@@ -796,19 +795,18 @@ class RemoveWorkCalendar(bpy.types.Operator):
"sequence.remove_work_calendar", self.file, **{"work_calendar": self.file.by_id(self.work_calendar)} "sequence.remove_work_calendar", self.file, **{"work_calendar": self.file.by_id(self.work_calendar)}
) )
Data.load(self.file) Data.load(self.file)
bpy.ops.bim.load_work_calendars()
return {"FINISHED"} return {"FINISHED"}
class EnableEditingWorkCalendar(bpy.types.Operator): class EnableEditingWorkCalendar(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar" bl_idname = "bim.enable_editing_work_calendar"
bl_label = "Enable Editing Work Plan" bl_label = "Enable Editing Work Calendar"
work_calendar: bpy.props.IntProperty() work_calendar: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
props = context.scene.BIMWorkCalendarProperties self.props = context.scene.BIMWorkCalendarProperties
while len(props.work_calendar_attributes) > 0: while len(self.props.work_calendar_attributes) > 0:
props.work_calendar_attributes.remove(0) self.props.work_calendar_attributes.remove(0)
data = Data.work_calendars[self.work_calendar] data = Data.work_calendars[self.work_calendar]
@@ -816,7 +814,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity": if data_type == "entity":
continue continue
new = props.work_calendar_attributes.add() new = self.props.work_calendar_attributes.add()
new.name = attribute.name() new.name = attribute.name()
new.is_null = data[attribute.name()] is None new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional() new.is_optional = attribute.optional()
@@ -827,7 +825,8 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]: if data[attribute.name()]:
new.enum_value = data[attribute.name()] new.enum_value = data[attribute.name()]
props.active_work_calendar_id = self.work_calendar self.props.active_work_calendar_id = self.work_calendar
self.props.is_editing = "ATTRIBUTES"
return {"FINISHED"} return {"FINISHED"}
@@ -848,6 +847,7 @@ class ImportP6(bpy.types.Operator, ImportHelper):
def execute(self, context): def execute(self, context):
from ifcp6.p62ifc import P62Ifc from ifcp6.p62ifc import P62Ifc
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
start = time.time() start = time.time()
p62ifc = P62Ifc() p62ifc = P62Ifc()
@@ -858,3 +858,268 @@ class ImportP6(bpy.types.Operator, ImportHelper):
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
print("Import finished in {:.2f} seconds".format(time.time() - start)) print("Import finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"} return {"FINISHED"}
class EnableEditingWorkCalendarTimes(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar_times"
bl_label = "Enable Editing Work Calendar Times"
work_calendar: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkCalendarProperties
props.active_work_calendar_id = self.work_calendar
props.is_editing = "WORKTIMES"
return {"FINISHED"}
class AddWorkTime(bpy.types.Operator):
bl_idname = "bim.add_work_time"
bl_label = "Add Work Time"
work_calendar: bpy.props.IntProperty()
time_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.add_work_time",
self.file,
**{"work_calendar": self.file.by_id(self.work_calendar), "time_type": self.time_type},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EnableEditingWorkTime(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_time"
bl_label = "Enable Editing Work Time"
work_time: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
while len(self.props.work_time_attributes) > 0:
self.props.work_time_attributes.remove(0)
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()]
self.initialise_recurrence_components()
self.load_recurrence_pattern_data(data)
self.props.active_work_time_id = self.work_time
return {"FINISHED"}
def initialise_recurrence_components(self):
if len(self.props.day_components) == 0:
for i in range(0, 31):
new = self.props.day_components.add()
new.name = str(i + 1)
if len(self.props.weekday_components) == 0:
for d in ["M", "T", "W", "T", "F", "S", "S"]:
new = self.props.weekday_components.add()
new.name = d
if len(self.props.month_components) == 0:
for m in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]:
new = self.props.month_components.add()
new.name = m
def load_recurrence_pattern_data(self, work_time):
self.props.position = 0
self.props.interval = 0
self.props.occurrences = 0
self.props.start_time = ""
self.props.end_time = ""
for component in self.props.day_components:
component.is_specified = False
for component in self.props.weekday_components:
component.is_specified = False
for component in self.props.month_components:
component.is_specified = False
if not work_time["RecurrencePattern"]:
return
recurrence_pattern = Data.recurrence_patterns[work_time["RecurrencePattern"]]
for attribute in ["Position", "Interval", "Occurrences"]:
if recurrence_pattern[attribute]:
setattr(self.props, attribute.lower(), recurrence_pattern[attribute])
for component in recurrence_pattern["DayComponent"] or []:
self.props.day_components[component - 1].is_specified = True
for component in recurrence_pattern["WeekdayComponent"] or []:
self.props.weekday_components[component - 1].is_specified = True
for component in recurrence_pattern["MonthComponent"] or []:
self.props.month_components[component - 1].is_specified = True
class DisableEditingWorkTime(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_time"
bl_label = "Disable Editing Work Time"
def execute(self, context):
context.scene.BIMWorkCalendarProperties.active_work_time_id = 0
return {"FINISHED"}
class EditWorkTime(bpy.types.Operator):
bl_idname = "bim.edit_work_time"
bl_label = "Edit Work Time"
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
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_work_time",
self.file,
**{"work_time": self.file.by_id(self.props.active_work_time_id), "attributes": attributes},
)
work_time = Data.work_times[self.props.active_work_time_id]
if work_time["RecurrencePattern"]:
self.edit_recurrence_pattern(work_time["RecurrencePattern"])
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_work_time()
return {"FINISHED"}
def edit_recurrence_pattern(self, recurrence_pattern_id):
recurrence_pattern = self.file.by_id(recurrence_pattern_id)
attributes = {
"Interval": self.props.interval if self.props.interval > 0 else None,
"Occurrences": self.props.occurrences if self.props.occurrences > 0 else None,
}
applicable_data = {
"DAILY": ["Interval", "Occurrences"],
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
"BY_DAY_COUNT": ["Interval", "Occurrences"],
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
}
if "Position" in applicable_data[recurrence_pattern.RecurrenceType]:
attributes["Position"] = self.props.position if self.props.position != 0 else None
if "DayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
attributes["DayComponent"] = [i + 1 for i, c in enumerate(self.props.day_components) if c.is_specified]
if "WeekdayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
attributes["WeekdayComponent"] = [
i + 1 for i, c in enumerate(self.props.weekday_components) if c.is_specified
]
if "MonthComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
attributes["MonthComponent"] = [i + 1 for i, c in enumerate(self.props.month_components) if c.is_specified]
ifcopenshell.api.run(
"sequence.edit_recurrence_pattern",
self.file,
**{"recurrence_pattern": recurrence_pattern, "attributes": attributes},
)
class RemoveWorkTime(bpy.types.Operator):
bl_idname = "bim.remove_work_time"
bl_label = "Remove Work Plan"
work_time: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.remove_work_time", self.file, **{"work_time": self.file.by_id(self.work_time)})
Data.load(self.file)
return {"FINISHED"}
class AssignRecurrencePattern(bpy.types.Operator):
bl_idname = "bim.assign_recurrence_pattern"
bl_label = "Assign Recurrence Pattern"
work_time: bpy.props.IntProperty()
recurrence_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_recurrence_pattern",
self.file,
**{"parent": self.file.by_id(self.work_time), "recurrence_type": self.recurrence_type},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnassignRecurrencePattern(bpy.types.Operator):
bl_idname = "bim.unassign_recurrence_pattern"
bl_label = "Unassign Recurrence Pattern"
recurrence_pattern: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_recurrence_pattern",
self.file,
**{"recurrence_pattern": self.file.by_id(self.recurrence_pattern)},
)
Data.load(self.file)
return {"FINISHED"}
class AddTimePeriod(bpy.types.Operator):
bl_idname = "bim.add_time_period"
bl_label = "Add Time Period"
recurrence_pattern: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
self.file = IfcStore.get_file()
try:
start_time = parser.parse(self.props.start_time)
end_time = parser.parse(self.props.end_time)
except:
return {"FINISHED"}
ifcopenshell.api.run(
"sequence.add_time_period",
self.file,
**{
"recurrence_pattern": self.file.by_id(self.recurrence_pattern),
"start_time": start_time,
"end_time": end_time,
},
)
self.props.start_time = ""
self.props.end_time = ""
Data.load(IfcStore.get_file())
return {"FINISHED"}
class RemoveTimePeriod(bpy.types.Operator):
bl_idname = "bim.remove_time_period"
bl_label = "Remove Time Period"
time_period: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_time_period",
self.file,
**{"time_period": self.file.by_id(self.time_period)},
)
Data.load(self.file)
return {"FINISHED"}
@@ -107,6 +107,13 @@ def updateTaskTimeDateTime(self, context, startfinish):
setattr(self, startfinish, canonicalise_time(startfinish_datetime)) setattr(self, startfinish, canonicalise_time(startfinish_datetime))
workschedule_enum = []
def getWorkSchedules(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()]
class Task(PropertyGroup): class Task(PropertyGroup):
name: StringProperty(name="Name", update=updateTaskName) name: StringProperty(name="Name", update=updateTaskName)
identification: StringProperty(name="Identification", update=updateTaskIdentification) identification: StringProperty(name="Identification", update=updateTaskIdentification)
@@ -128,10 +135,11 @@ class WorkPlan(PropertyGroup):
class BIMWorkPlanProperties(PropertyGroup): class BIMWorkPlanProperties(PropertyGroup):
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute) work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: StringProperty(name="Is Editing")
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan) work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
active_work_plan_index: IntProperty(name="Active Work Plan Index") active_work_plan_index: IntProperty(name="Active Work Plan Index")
active_work_plan_id: IntProperty(name="Active Work Plan Id") active_work_plan_id: IntProperty(name="Active Work Plan Id")
work_schedules: EnumProperty(items=getWorkSchedules, name="Work Schedules")
class BIMWorkScheduleProperties(PropertyGroup): class BIMWorkScheduleProperties(PropertyGroup):
@@ -150,7 +158,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
class BIMTaskTreeProperties(PropertyGroup): class BIMTaskTreeProperties(PropertyGroup):
# This belongs by itself for performance reasons. # This belongs by itself for performance reasons. https://developer.blender.org/T87737
# In Blender if you add thousands of tasks it makes other property access in the same group really slow. # In Blender if you add thousands of tasks it makes other property access in the same group really slow.
tasks: CollectionProperty(name="Tasks", type=Task) tasks: CollectionProperty(name="Tasks", type=Task)
@@ -160,9 +168,34 @@ class WorkCalendar(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
class RecurrenceComponent(PropertyGroup):
name: StringProperty(name="Name")
is_specified: BoolProperty(name="Is Specified")
class BIMWorkCalendarProperties(PropertyGroup): class BIMWorkCalendarProperties(PropertyGroup):
work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute) work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False) work_time_attributes: CollectionProperty(name="Work Time Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing")
work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar) work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar)
active_work_calendar_index: IntProperty(name="Active Work Calendar Index")
active_work_calendar_id: IntProperty(name="Active Work Calendar Id") active_work_calendar_id: IntProperty(name="Active Work Calendar Id")
active_work_time_id: IntProperty(name="Active Work Time Id")
day_components: CollectionProperty(name="Day Components", type=RecurrenceComponent)
weekday_components: CollectionProperty(name="Weekday Components", type=RecurrenceComponent)
month_components: CollectionProperty(name="Month Components", type=RecurrenceComponent)
position: IntProperty(name="Position")
interval: IntProperty(name="Recurrence Interval")
occurrences: IntProperty(name="Occurs N Times")
recurrence_types: EnumProperty(items=[
("DAILY", "Daily", "e.g. Every day"),
("WEEKLY", "Weekly", "e.g. Every Friday"),
("MONTHLY_BY_DAY_OF_MONTH", "Monthly on Specified Date", "e.g. Every 2nd of each Month"),
("MONTHLY_BY_POSITION", "Monthly on Specified Weekday", "e.g. Every 1st Friday of each Month"),
# https://forums.buildingsmart.org/t/what-does-by-day-count-and-by-weekday-count-mean-in-ifcrecurrencetypeenum/3571
# ("BY_DAY_COUNT", "", ""),
# ("BY_WEEKDAY_COUNT", "", ""),
("YEARLY_BY_DAY_OF_MONTH", "Yearly on Specified Date", "e.g. Every 2nd of October"),
("YEARLY_BY_POSITION", "Yearly on Specified Weekday", "e.g. Every 1st Friday of October"),
], name="Recurrence Types")
start_time: StringProperty(name="Start Time")
end_time: StringProperty(name="End Time")
@@ -19,28 +19,36 @@ class BIM_PT_work_plans(Panel):
if not Data.is_loaded: if not Data.is_loaded:
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkPlanProperties self.props = context.scene.BIMWorkPlanProperties
row = self.layout.row()
row.operator("bim.add_work_plan", icon="ADD")
for work_plan_id, work_plan in Data.work_plans.items():
self.draw_work_plan_ui(work_plan_id, work_plan)
def draw_work_plan_ui(self, work_plan_id, work_plan):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Work Plans Found".format(len(Data.work_plans)), icon="TEXT") row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_plan", text="", icon="ADD") if self.props.active_work_plan_id == work_plan_id:
row.operator("bim.disable_work_plan_editing_ui", text="", icon="CHECKMARK") row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_plan", text="", icon="CANCEL")
elif self.props.active_work_plan_id:
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id
else: else:
row.operator("bim.load_work_plans", text="", icon="GREASEPENCIL") op = row.operator("bim.enable_editing_work_plan_schedules", text="", icon="LINENUMBERS_ON")
op.work_plan = work_plan_id
op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL")
op.work_plan = work_plan_id
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id
if self.props.is_editing: if self.props.active_work_plan_id == work_plan_id:
self.layout.template_list( if self.props.is_editing == "ATTRIBUTES":
"BIM_UL_work_plans", self.draw_editable_ui()
"", elif self.props.is_editing == "SCHEDULES":
self.props, self.draw_work_schedule_ui()
"work_plans",
self.props,
"active_work_plan_index",
)
if self.props.active_work_plan_id: def draw_editable_ui(self):
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.work_plan_attributes: for attribute in self.props.work_plan_attributes:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if attribute.data_type == "string": if attribute.data_type == "string":
@@ -50,21 +58,20 @@ class BIM_PT_work_plans(Panel):
if attribute.is_optional: if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_work_schedule_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "work_schedules", text="")
op = row.operator("bim.assign_work_schedule", text="", icon="ADD")
op.work_plan = self.props.active_work_plan_id
op.work_schedule = int(self.props.work_schedules)
class BIM_UL_work_plans(UIList): for work_schedule_id in Data.work_plans[self.props.active_work_plan_id]["IsDecomposedBy"]:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): work_schedule = Data.work_schedules[work_schedule_id]
if item: row = self.layout.row(align=True)
row = layout.row(align=True) row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
row.label(text=item.name) op = row.operator("bim.unassign_work_schedule", text="", icon="X")
if context.scene.BIMWorkPlanProperties.active_work_plan_id == item.ifc_definition_id: op.work_plan = self.props.active_work_plan_id
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK") op.work_schedule = int(self.props.work_schedules)
row.operator("bim.disable_editing_work_plan", text="", icon="X")
elif context.scene.BIMWorkPlanProperties.active_work_plan_id:
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL")
op.work_plan = item.ifc_definition_id
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
class BIM_PT_work_schedules(Panel): class BIM_PT_work_schedules(Panel):
@@ -108,7 +115,9 @@ class BIM_PT_work_schedules(Panel):
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
else: else:
row.operator("bim.enable_editing_tasks", text="", icon="ACTION").work_schedule = work_schedule_id row.operator("bim.enable_editing_tasks", text="", icon="ACTION").work_schedule = work_schedule_id
row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL").work_schedule = work_schedule_id row.operator(
"bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL"
).work_schedule = work_schedule_id
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
if self.props.active_work_schedule_id == work_schedule_id: if self.props.active_work_schedule_id == work_schedule_id:
@@ -258,29 +267,73 @@ class BIM_PT_work_calendars(Panel):
if not Data.is_loaded: if not Data.is_loaded:
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkCalendarProperties self.props = context.scene.BIMWorkCalendarProperties
row = self.layout.row()
row.operator("bim.add_work_calendar", icon="ADD")
for work_calendar_id, work_calendar in Data.work_calendars.items():
self.draw_work_calendar_ui(work_calendar_id, work_calendar)
def draw_work_calendar_ui(self, work_calendar_id, work_calendar):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Work Calendar Found".format(len(Data.work_calendars)), icon="TEXT") row.label(text=work_calendar["Name"] or "Unnamed", icon="VIEW_ORTHO")
if self.props.is_editing: if self.props.active_work_calendar_id == work_calendar_id:
row.operator("bim.add_work_calendar", text="", icon="ADD") row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
row.operator("bim.disable_work_calendar_editing_ui", text="", icon="CHECKMARK") row.operator("bim.disable_editing_work_calendar", text="", icon="CANCEL")
elif self.props.active_work_calendar_id:
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = work_calendar_id
else: else:
row.operator("bim.load_work_calendars", text="", icon="GREASEPENCIL") op = row.operator("bim.enable_editing_work_calendar_times", text="", icon="MESH_GRID")
op.work_calendar = work_calendar_id
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL")
op.work_calendar = work_calendar_id
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = work_calendar_id
if self.props.is_editing: if self.props.active_work_calendar_id == work_calendar_id:
self.layout.template_list( if self.props.is_editing == "ATTRIBUTES":
"BIM_UL_work_calendars", self.draw_editable_ui()
"", elif self.props.is_editing == "WORKTIMES":
self.props, self.draw_work_times_ui(work_calendar_id, work_calendar)
"work_calendars",
self.props,
"active_work_calendar_index",
)
if self.props.active_work_calendar_id: def draw_work_times_ui(self, work_calendar_id, work_calendar):
self.draw_editable_ui(context) row = self.layout.row(align=True)
op = row.operator("bim.add_work_time", text="Add Work Time", icon="ADD")
op.work_calendar = work_calendar_id
op.time_type = "WorkingTimes"
op = row.operator("bim.add_work_time", text="Add Exception Time", icon="ADD")
op.work_calendar = work_calendar_id
op.time_type = "ExceptionTimes"
def draw_editable_ui(self, context): for work_time_id in work_calendar["WorkingTimes"]:
for attribute in self.props.work_calendar_attributes: self.draw_work_time_ui(Data.work_times[work_time_id], time_type="WorkingTimes")
for work_time_id in work_calendar["ExceptionTimes"]:
self.draw_work_time_ui(Data.work_times[work_time_id], time_type="ExceptionTimes")
def draw_work_time_ui(self, work_time, time_type):
row = self.layout.row(align=True)
row.label(
text=work_time["Name"] or "Unnamed", icon="MESH_GRID" if time_type == "WorkingTimes" else "LIGHTPROBE_GRID"
)
if work_time["Start"] or work_time["Finish"]:
row.label(text="{} - {}".format(work_time["Start"] or "*", work_time["Finish"] or "*"))
if self.props.active_work_time_id == work_time["id"]:
row.operator("bim.edit_work_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_time", text="", icon="CANCEL")
elif self.props.active_work_time_id:
op = row.operator("bim.remove_work_time", text="", icon="X")
op.work_time = work_time["id"]
else:
op = row.operator("bim.enable_editing_work_time", text="", icon="GREASEPENCIL")
op.work_time = work_time["id"]
op = row.operator("bim.remove_work_time", text="", icon="X")
op.work_time = work_time["id"]
if self.props.active_work_time_id == work_time["id"]:
self.draw_editable_work_time_ui(work_time)
def draw_editable_work_time_ui(self, work_time):
for attribute in self.props.work_time_attributes:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if attribute.data_type == "string": if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name) row.prop(attribute, "string_value", text=attribute.name)
@@ -289,18 +342,78 @@ class BIM_PT_work_calendars(Panel):
if attribute.is_optional: if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if work_time["RecurrencePattern"]:
self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]])
else:
row = self.layout.row(align=True)
row.prop(self.props, "recurrence_types", icon="RECOVER_LAST", text="")
op = row.operator("bim.assign_recurrence_pattern", icon="ADD", text="")
op.work_time = work_time["id"]
op.recurrence_type = self.props.recurrence_types
class BIM_UL_work_calendars(UIList): def draw_editable_recurrence_pattern_ui(self, recurrence_pattern):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): box = self.layout.box()
if item: row = box.row(align=True)
row = layout.row(align=True) row.label(text=recurrence_pattern["RecurrenceType"], icon="RECOVER_LAST")
row.label(text=item.name) op = row.operator("bim.unassign_recurrence_pattern", text="", icon="X")
if context.scene.BIMWorkCalendarProperties.active_work_calendar_id == item.ifc_definition_id: op.recurrence_pattern = recurrence_pattern["id"]
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_calendar", text="", icon="X") row = box.row(align=True)
elif context.scene.BIMWorkCalendarProperties.active_work_calendar_id: row.prop(self.props, "start_time", text="")
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id row.prop(self.props, "end_time", text="")
else: op = row.operator("bim.add_time_period", text="", icon="ADD")
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL") op.recurrence_pattern = recurrence_pattern["id"]
op.work_calendar = item.ifc_definition_id
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id for time_period_id in recurrence_pattern["TimePeriods"]:
time_period = Data.time_periods[time_period_id]
row = box.row(align=True)
row.label(text="{} - {}".format(time_period["StartTime"], time_period["EndTime"]), icon="TIME")
op = row.operator("bim.remove_time_period", text="", icon="X")
op.time_period = time_period_id
applicable_data = {
"DAILY": ["Interval", "Occurrences"],
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
"BY_DAY_COUNT": ["Interval", "Occurrences"],
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
}
if "Position" in applicable_data[recurrence_pattern["RecurrenceType"]]:
row = box.row()
row.prop(self.props, "position")
if "DayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
for i, component in enumerate(self.props.day_components):
if i % 7 == 0:
row = box.row(align=True)
row.prop(component, "is_specified", text=component.name)
if "WeekdayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
row = box.row(align=True)
for component in self.props.weekday_components:
row.prop(component, "is_specified", text=component.name)
if "MonthComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
for i, component in enumerate(self.props.month_components):
if i % 4 == 0:
row = box.row(align=True)
row.prop(component, "is_specified", text=component.name)
row = box.row()
row.prop(self.props, "interval")
row = box.row()
row.prop(self.props, "occurrences")
def draw_editable_ui(self):
for attribute in self.props.work_calendar_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_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="")
+11 -2
View File
@@ -190,13 +190,22 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox-bin', path]]") svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox-bin', path]]")
pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox-bin', path]]") pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox-bin', path]]")
should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True) should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True)
should_play_chaching_sound: BoolProperty(
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
)
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
row = layout.row() row = layout.row()
row.label(text="To upgrade, first uninstall your current BlenderBIM Add-on, then install the new version.", icon="ERROR") row.label(
text="To upgrade, first uninstall your current BlenderBIM Add-on, then install the new version.",
icon="ERROR",
)
row = layout.row() row = layout.row()
row.label(text="To uninstall, first disable the add-on. Then restart Blender before pressing the 'Remove' button.", icon="ERROR") row.label(
text="To uninstall, first disable the add-on. Then restart Blender before pressing the 'Remove' button.",
icon="ERROR",
)
row = layout.row() row = layout.row()
row.operator("bim.open_upstream", text="Visit Homepage").page = "home" row.operator("bim.open_upstream", text="Visit Homepage").page = "home"
row.operator("bim.open_upstream", text="Visit Documentation").page = "docs" row.operator("bim.open_upstream", text="Visit Documentation").page = "docs"
+21
View File
@@ -0,0 +1,21 @@
# bsdd
An experimental work in progress library to interact with the buildingSMART Data Dictionary (bSDD) API.
More reading:
* [Swagger API docs](https://bs-dd-api-prototype.azurewebsites.net/swagger/index.html)
* [bSDD Github Repository](https://github.com/buildingSMART/bSDD)
# Demo
Let's replicate the SketchUp example:
```
client = Client()
pprint(client.Domain())
pprint(client.SearchListOpen("http://identifier.buildingsmart.org/uri/nlsfb/nlsfb2005-2.2", RelatedIfcEntity="IfcWall"))
data = client.Classification("http://identifier.buildingsmart.org/uri/nlsfb/nlsfb2005-2.2/class/21.21")
pprint(data)
apply_ifc_classification_properties(ifc_file, element, data["classificationProperties"])
```
+179
View File
@@ -0,0 +1,179 @@
import uuid
import time
import json
import urllib
import requests
import webbrowser
import http.server
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
def do_GET(self):
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
self.server.auth_code = query.get("code", [""])[0]
self.server.auth_state = query.get("state", [""])[0]
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
class Client:
def __init__(self):
self.baseurl = "https://bs-dd-api-prototype.azurewebsites.net/"
self.access_token = ""
self.refresh_token = ""
self.access_token_expires_on = time.time()
self.refresh_token_expires_on = time.time()
self.auth_endpoint = "https://buildingsmartservices.b2clogin.com/tfp/buildingsmartservices.onmicrosoft.com/b2c_1_signupsignin/oauth2/v2.0/authorize"
self.token_endpoint = "https://buildingsmartservices.b2clogin.com/tfp/buildingsmartservices.onmicrosoft.com/b2c_1_signupsignin/oauth2/v2.0/token"
self.client_id = "4aba821f-d4ff-498b-a462-c2837dbbba70"
def get(self, endpoint, params=None, is_auth_required=False):
headers = {}
if is_auth_required:
headers = {"Authorization": "Bearer " + self.get_access_token()}
return requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None).json()
def post(self):
pass # TODO
def get_access_token(self):
if self.access_token and self.access_token_expires_on > time.time():
return self.access_token
elif self.refresh_token and self.refresh_token_expires_on > time.time():
self.refresh_token()
else:
self.login()
return self.access_token
def login(self):
with http.server.HTTPServer(("", 0), OAuthReceiver) as server:
state = str(uuid.uuid4())
query = urllib.parse.urlencode(
{
"client_id": self.client_id,
"response_type": "code",
# offline_access required to get a refresh_token
"scope": "https://buildingsmartservices.onmicrosoft.com/api/read offline_access",
"state": state,
"redirect_uri": f"http://localhost:{server.server_address[1]}",
}
)
webbrowser.open(f"{self.auth_endpoint}?{query}")
server.timeout = 75
server.state = state
server.handle_request()
if server.auth_code and server.auth_state == state:
self.set_tokens_from_response(
requests.post(
"https://buildingsmartservices.b2clogin.com/tfp/buildingsmartservices.onmicrosoft.com/b2c_1_signupsignin/oauth2/v2.0/token",
params={
"grant_type": "authorization_code",
"code": server.auth_code,
},
).json()
)
def refresh_token(self):
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
params={
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
},
).json()
)
def set_tokens_from_response(self, response):
self.access_token = response["access_token"]
self.refresh_token = response["refresh_token"]
self.access_token_expires_on = time.time() + response["expires_in"]
self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
def Classification(self, namespaceUri, version="v3", languageCode="", includeChildClassificationReferences=True):
return self.get(
f"api/Classification/{version}",
{
"namespaceUri": namespaceUri,
"languageCode": languageCode,
"includeChildClassificationReferences": includeChildClassificationReferences,
},
)
def Country(self, version="v1"):
return self.get(f"api/Country/{version}")
def Domain(self, version="v2"):
return self.get(f"api/Domain/{version}")
def graphql(self):
return # TODO
def Language(self, version="v1"):
return self.get(f"api/Language/{version}")
def Property(self, namespaceUri, version="v2", languageCode=""):
return self.get(f"api/Property/{version}", {"namespaceUri": namespaceUri, "languageCode": languageCode})
def PropertyValue(self, namespaceUri, version="v1", languageCode=""):
return self.get(f"api/PropertyValue/{version}", {"namespaceUri": namespaceUri, "languageCode": languageCode})
def ReferenceDocument(self, version="v1"):
return self.get(f"api/ReferenceDocument/{version}")
def RequestExportFile(self):
return # TODO
def SearchList(self, DomainNamespaceUri, version="v2", SearchText="", LanguageCode="", RelatedIfcEntity=""):
return self.get(
f"api/SearchList/{version}",
{
"DomainNamespaceUri": DomainNamespaceUri,
"SearchText": SearchText,
"LanguageCode": LanguageCode,
"RelatedIfcEntity": RelatedIfcEntity,
},
is_auth_required=True,
)
def SearchListOpen(self, DomainNamespaceUri, version="v2", SearchText="", LanguageCode="", RelatedIfcEntity=""):
return self.get(
f"api/SearchListOpen/{version}",
{
"DomainNamespaceUri": DomainNamespaceUri,
"SearchText": SearchText,
"LanguageCode": LanguageCode,
"RelatedIfcEntity": RelatedIfcEntity,
},
)
def TextSearchListOpen(self):
return # TODO
def Unit(self, version="v1"):
return self.get(f"api/Unit/{version}")
def UploadImportFile(self):
return # TODO
def apply_ifc_classification_properties(ifc_file, element, classificationProperties):
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
psets = ifcopenshell.util.element.get_psets(element)
for prop in classificationProperties:
predefinedValue = prop.get("predefinedValue")
if not predefinedValue or prop.get("propertyDomainName") != "IFC":
continue
pset = psets.get(prop["propertySet"])
if pset:
pset = ifc_file.by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name=prop["propertySet"])
if prop["dataType"] == "boolean":
predefinedValue = predefinedValue == "TRUE"
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={prop["name"]: predefinedValue})
+35
View File
@@ -0,0 +1,35 @@
# IFCCityJSON
Converter for CityJSON files and IFC. Currently only supports one-way conversion from CityJSON to IFC.
-- WARNING --
IFCCityJSON only came into being 14/04/2021. Be prepared for lots of bugs, unfinished implementations and little to no documentation!
## Dependencies
- [IfcOpenShell](https://github.com/IfcOpenShell/IfcOpenShell)
- [CJIO](https://github.com/cityjson/cjio)
## Usage of IFCCityJSON
Following command will execute a conversion from CityJSON to IFC
python ifccityjson.py [-i input file] [-o output file] [-n name of identification attribute]
The example file that could be used is example/3D_BAG_example.json
python ifccityjson.py -i example/3DBAG_example.json -o example/3DBAG_example.ifc -n identificatie
## Implemented geometries
- [ ] "MultiPoint"
- [ ] "MultiLineString"
- [x] "MultiSurface"
- [ ] "CompositeSurface"
- [x] "Solid": exterior shell
- [ ] "Solid": interior shell
- [x] "MultiSolid"
- [x] "CompositeSolid"
- [ ] "GeometryInstance"
## TODO
- [x] CityJSON Attributes as IFC properties in 'CityJSON_attributes' pset
- [x] Implement georeferencing
- [ ] Do not use template IFC for new IFC file, but make IFC file from scratch
+190
View File
@@ -0,0 +1,190 @@
import ifcopenshell
import warnings
from geometry import GeometryIO
JSON_TO_IFC = {
"Building": ["IfcBuilding"],
"BuildingPart": ["IfcBuilding", {"CompositionType": "Partial"}], # CompositionType: Partial
"BuildingInstallation": ["IfcDistributionElement"],
"Road": ["IfcCivilElement"],
"TransportSquare": ["IfcSpace"],
"TINRelief": ["IfcGeographicElement"],
"WaterBody": ["IfcGeographicElement"],
"LandUse": ["IfcGeographicElement"],
"PlantCover": ["IfcGeographicElement"],
"SolitaryVegetationObject": ["IfcGeographicElement"],
"CityFurniture": ["IfcFurnishingElement"],
"GenericCityObject": ["IfcCivilElement"],
"Bridge": ["IfcCivilElement"],
"BridgePart": ["IfcCivilElement"],
"BridgeInstallation": ["IfcCivilElement"],
"BridgeConstructionElement": ["IfcCivilElement"],
"Tunnel": ["IfcCivilElement"],
"TunnelPart": ["IfcCivilElement"],
"TunnelInstallation": ["IfcCivilElement"],
"CityObjectGroup": ["IfcCivilElement"],
"GroundSurface": ["IfcSlab"],
"RoofSurface": ["IfcRoof"],
"WallSurface": ["IfcWall"]
}
class Cityjson2ifc:
def __init__(self):
self.city_model = None
self.IFC_model = None
self.properties = {}
self.geometry = GeometryIO()
self.configuration()
def configuration(self, file_destination="output.ifc", name_attribute=None):
self.properties["file_destination"] = file_destination
self.properties["name_attribute"] = name_attribute
def convert(self, city_model):
self.city_model = city_model
self.create_new_file()
self.create_metadata()
self.geometry.build_vertices(self.IFC_model,
coords=city_model.j["vertices"],
scale=self.properties["local_scale"])
# self.build_vertices()
self.create_IFC_classes()
self.write_file()
def create_metadata(self):
# Georeferencing
self.properties["local_translation"] = None
self.properties["local_scale"] = None
if self.city_model.is_transform():
self.properties["local_scale"] = self.city_model.j['transform']['scale']
local_translation = self.city_model.j['transform']['translate']
self.properties["local_translation"] = {
"Eastings": local_translation[0],
"Northings": local_translation[1],
"OrthogonalHeight": local_translation[2]
}
epsg = self.city_model.get_epsg()
if epsg:
# Meter is assumed as unit for now
unit = self.IFC_model.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
crs = self.IFC_model.create_entity("IfcProjectedCrs", Name=f"epsg:{epsg}")
self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, **self.properties["local_translation"])
self.properties["owner_history"] = self.IFC_model.by_type("IfcOwnerHistory")[0]
def create_new_file(self):
self.IFC_model = ifcopenshell.open('example/template.ifc')
self.IFC_site = self.IFC_model.by_type('IfcSite')[0]
self.IFC_representation_sub_context = self.IFC_model.by_type("IFCGEOMETRICREPRESENTATIONSUBCONTEXT")[0]
self.IFC_representation_context = self.IFC_model.by_type("IFCGEOMETRICREPRESENTATIONCONTEXT")[0]
# self.IFC_model = ifcopenshell.file(schema='IFC4')
def write_file(self):
self.IFC_model.write(self.properties["file_destination"])
def create_IFC_classes(self):
for obj_id, obj in self.city_model.get_cityobjects().items():
# CityJSON type to class
mapping = JSON_TO_IFC[obj.type]
IFC_class = mapping[0]
data = {}
# Add attributes if it is specified in mapping
# Example: BuildingPart to IfcBuilding with CompositionType: Partial
if len(mapping) > 1:
data.update(mapping[1])
# attributes
IFC_name = None
if "name_attribute" in self.properties and self.properties["name_attribute"] in obj.attributes:
IFC_name = obj.attributes[self.properties["name_attribute"]]
# TODO children
# TODO parents
# TODO geometry_type
# geometry_lod
lod = 0
geometry = None
for geom in obj.geometry:
if geom.lod > lod:
geometry = geom
lod = geom.lod
IFC_children = []
if geometry.surfaces:
for surface_id in geometry.surfaces:
IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0]
child_data = {"GlobalId": ifcopenshell.guid.new(),
"Name": IFC_child_class
}
# CREATE ENTITY
surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id)
if surface_geometry:
child_data["Representation"] = self.create_IFC_representation(surface_geometry)
IFC_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data))
else:
IFC_geometry = self.geometry.create_IFC_geometry(self.IFC_model, geometry)
if IFC_geometry:
data["Representation"] = self.create_IFC_representation(IFC_geometry)
data["GlobalId"] = ifcopenshell.guid.new()
data["Name"] = IFC_name
IFC_object = self.IFC_model.create_entity(IFC_class, **data)
# Define aggregation
self.IFC_model.create_entity("IfcRelAggregates",
**{"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": [IFC_object],
"RelatingObject": self.IFC_site}
)
if IFC_children:
self.IFC_model.create_entity("IfcRelAggregates",
**{"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": IFC_children,
"RelatingObject": IFC_object})
self.create_property_set(obj.attributes, IFC_object)
def create_IFC_representation(self, IFC_geometry):
shape_representation = self.IFC_model.create_entity("IfcShapeRepresentation",
self.IFC_representation_sub_context, 'Body', 'Brep',
[IFC_geometry])
product_representation = self.IFC_model.create_entity("IfcProductDefinitionShape",
Representations=[shape_representation])
return product_representation
def create_property_set(self, CJ_attributes, IFC_entity):
IFC_object_properties = []
for property, val in CJ_attributes.items():
if val == None:
continue
if type(val) == int:
IFC_type = "IfcInteger"
elif type(val) == float:
IFC_type = "IfcReal"
elif type(val) == bool:
IFC_type = "IfcBoolean"
else:
IFC_type = "IfcText"
IFC_object_properties.append(
self.IFC_model.createIfcPropertySingleValue(property, property,
self.IFC_model.create_entity(IFC_type, val), None)
)
property_set = self.IFC_model.createIfcPropertySet(ifcopenshell.guid.new(),
self.properties["owner_history"],
"CityJSON_attributes",
None,
IFC_object_properties)
self.IFC_model.createIfcRelDefinesByProperties(ifcopenshell.guid.new(),
self.properties["owner_history"],
None, None, [IFC_entity],
property_set)
File diff suppressed because one or more lines are too long
+39
View File
@@ -0,0 +1,39 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('template.ifc','2021-03-17T14:54:00+11:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$);
#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$);
#5=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$);
#6=IFCTELECOMADDRESS(.USERDEFINED.,'The CJ2IFC webpage of the software collection.','WEBPAGE',$,$,$,$,'https://github.com/LaurensJN/IFC2JS',$);
#7=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$);
#8=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#4),(#5,#6,#7));
#9=IFCAPPLICATION(#8,'0.0.999999','CityJSON to IFC converter','CJ2IFC');
#10=IFCOWNERHISTORY(#3,#9,.READWRITE.,.ADDED.,1615934569,#3,#9,1615934569);
#11=IFCPROJECT('2yjpApQSX3ZAZudc2AGHb3',#10,'My Project',$,$,$,$,(#20,#27),#15);
#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#13=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#14=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#15=IFCUNITASSIGNMENT((#12,#13,#14));
#16=IFCCARTESIANPOINT((0.,0.,0.));
#17=IFCDIRECTION((0.,0.,1.));
#18=IFCDIRECTION((1.,0.,0.));
#19=IFCAXIS2PLACEMENT3D(#16,#17,#18);
#20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#19,$);
#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$);
#22=IFCOWNERHISTORY(#3,#9,.READWRITE.,.ADDED.,1615934569,#3,#9,1615934569);
#23=IFCSITE('1maxFQa9z7q8w50tN19Dw9',#22,'My Site',$,$,#30,$,$,$,$,$,$,$,$);
#24=IFCOWNERHISTORY(#3,#9,.READWRITE.,.ADDED.,1615934569,#3,#9,1615934569);
#25=IFCRELAGGREGATES('1GuJ2jz2TA49gFFSYhrqGa',#24,$,$,#11,(#23));
#26=IFCCARTESIANPOINT((0.,0.,0.));
#27=IFCDIRECTION((0.,0.,1.));
#28=IFCDIRECTION((1.,0.,0.));
#29=IFCAXIS2PLACEMENT3D(#26,#27,#28);
#30=IFCLOCALPLACEMENT($,#29);
ENDSEC;
END-ISO-10303-21;
+81
View File
@@ -0,0 +1,81 @@
import warnings
class GeometryIO:
def __init__(self):
self.vertices = {}
def build_vertices(self, IFC_model, coords, scale=None):
for coord in coords:
if scale:
IFC_vertex = tuple([float(xyz) * coord_scale
for xyz, coord_scale
in zip(coord, scale)])
else:
IFC_vertex = [float(xyz) for xyz in coord]
IFC_cartesian_point = IFC_model.create_entity("IfcCartesianPoint", IFC_vertex)
self.vertices[tuple(coord)] = IFC_cartesian_point
def create_IFC_geometry(self, IFC_model, geometry):
if geometry.type == "Solid":
return self.create_IFC_closed_shell(IFC_model, geometry)
elif geometry.type in ["CompositeSolid", "MultiSolid"]:
return self.create_IFC_composite_closed_shell(IFC_model, geometry)
else:
warnings.warn("Types other than solids are not yet supported")
return
def create_IFC_composite_closed_shell(self, IFC_model, geometry):
shells = []
for shell in geometry.boundaries:
outershell = shell[0]
faces = []
for face in outershell: # exterior shell
for triangle in face:
faces.append(self.create_IFC_face(IFC_model, triangle))
shells.append(IFC_model.create_entity("IfcClosedShell", faces))
IFC_geometry = IFC_model.create_entity("IfcShellBasedSurfaceModel", shells)
return IFC_geometry
def create_IFC_closed_shell(self, IFC_model, geometry):
outershell = geometry.boundaries[0]
# print(geometry.surfaces[0]['surface_idx'][0])
faces = []
for face in outershell: # exterior shell
for triangle in face:
faces.append(self.create_IFC_face(IFC_model, triangle))
if len(geometry.boundaries) == 1:
shell = IFC_model.create_entity("IfcClosedShell", faces)
IFC_geometry = IFC_model.create_entity("IfcShellBasedSurfaceModel", [shell])
return IFC_geometry
# TODO: INTERIOR SHELL
warnings.warn("Solid interior shell not yet supported")
return
# for boundary in geometry.boundaries[1]: # interior shell
# for face in boundary:
# for triangle in face:
# print(triangle)
# print(geometry.boundaries)
def create_IFC_surface(self, IFC_model, geometry, surface_id):
face_ids = geometry.surfaces[surface_id]["surface_idx"]
faces = []
for shell, face_id in face_ids:
for triangle in geometry.boundaries[shell][face_id]:
faces.append(self.create_IFC_face(IFC_model, triangle))
shell = IFC_model.create_entity("IfcOpenShell", faces)
IFC_geometry = IFC_model.create_entity("IfcShellBasedSurfaceModel", [shell])
return IFC_geometry
def create_IFC_face(self, IFC_model, face):
vertices = []
for vertex in face:
vertices.append(self.vertices[tuple(vertex)])
polyloop = IFC_model.create_entity("IfcPolyLoop", vertices)
outerbound = IFC_model.create_entity("IfcFaceOuterBound", polyloop, True)
return IFC_model.create_entity("IfcFace", [outerbound])
+24
View File
@@ -0,0 +1,24 @@
import argparse
from cjio import cityjson
from cityjson2ifc import Cityjson2ifc
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
# Example:
# python ifccityjson.py -i example/3DBAG_example.json -o example/output.ifc -n identificatie
parser = argparse.ArgumentParser(description="")
parser.add_argument("-i", "--input", type=str, help="input CityJSON file", required=True)
parser.add_argument("-o", "--output", type=str, help="output IFC file. Standard is output.ifc")
parser.add_argument("-n", "--name", type=str, help="Attribute containing the name")
args = parser.parse_args()
city_model = cityjson.load(args.input)
data = {}
if args.name:
data["name_attribute"] = args.name
if args.output:
data["file_destination"] = args.output
converter = Cityjson2ifc()
converter.configuration(**data)
converter.convert(city_model)
+2 -1
View File
@@ -352,7 +352,8 @@ public:
IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item); IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item);
const IfcSchema::IfcRepresentationItem* find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item); const IfcSchema::IfcRepresentationItem* find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item);
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid); bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid);
bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid); bool shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li);
bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid, bool force_sewing=false);
bool is_compound(const TopoDS_Shape& shape); bool is_compound(const TopoDS_Shape& shape);
bool is_convex(const TopoDS_Wire& wire); bool is_convex(const TopoDS_Wire& wire);
TopoDS_Shape halfspace_from_plane(const gp_Pln& pln,const gp_Pnt& cent); TopoDS_Shape halfspace_from_plane(const gp_Pln& pln,const gp_Pnt& cent);
+10 -7
View File
@@ -529,22 +529,25 @@ void IfcGeom::Kernel::set_rotation(const std::array<double, 4> &p_rotation) {
offset_and_rotation = combine_offset_and_rotation(offset, rotation); offset_and_rotation = combine_offset_and_rotation(offset, rotation);
} }
bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) { bool IfcGeom::Kernel::shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li) {
TopTools_ListOfShape face_list; TopExp_Explorer exp(s, TopAbs_FACE);
TopExp_Explorer exp(compound, TopAbs_FACE);
for (; exp.More(); exp.Next()) { for (; exp.More(); exp.Next()) {
TopoDS_Face face = TopoDS::Face(exp.Current()); TopoDS_Face face = TopoDS::Face(exp.Current());
face_list.Append(face); li.Append(face);
} }
return true;
}
bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) {
TopTools_ListOfShape face_list;
shape_to_face_list(compound, face_list);
if (face_list.Extent() == 0) { if (face_list.Extent() == 0) {
return false; return false;
} }
return create_solid_from_faces(face_list, shape); return create_solid_from_faces(face_list, shape);
} }
bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape) { bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape, bool force_sewing) {
bool valid_shell = false; bool valid_shell = false;
if (face_list.Extent() == 1) { if (face_list.Extent() == 1) {
@@ -565,7 +568,7 @@ bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_l
// found a case where this actually improves boolean ops later on. // found a case where this actually improves boolean ops later on.
// if (!faceset_helper_ || !faceset_helper_->non_manifold()) { // if (!faceset_helper_ || !faceset_helper_->non_manifold()) {
for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { for (face_iterator.Initialize(face_list); !force_sewing && face_iterator.More(); face_iterator.Next()) {
// As soon as is detected one of the edges is shared, the assumption is made no // As soon as is detected one of the edges is shared, the assumption is made no
// additional sewing is necessary. // additional sewing is necessary.
if (!has_shared_edges) { if (!has_shared_edges) {
+6 -12
View File
@@ -240,18 +240,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcExtrudedAreaSolidTapered* l, T
TopoDS_Shape result = builder.Shape(); TopoDS_Shape result = builder.Shape();
BRepOffsetAPI_Sewing sewer; TopTools_ListOfShape li;
sewer.SetTolerance(getValue(GV_PRECISION)); shape_to_face_list(result, li);
sewer.SetMaxTolerance(getValue(GV_PRECISION)); li.Append(BRepBuilderAPI_MakeFace(w1).Face().Reversed());
sewer.SetMinTolerance(getValue(GV_PRECISION)); li.Append(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
sewer.Add(result); create_solid_from_faces(li, result, true);
sewer.Add(BRepBuilderAPI_MakeFace(w1).Face());
sewer.Add(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
sewer.Perform();
result = sewer.SewedShape();
// @todo ugly hack // @todo ugly hack
+44 -25
View File
@@ -30,6 +30,7 @@
#include <Bnd_Box.hxx> #include <Bnd_Box.hxx>
#include <BRepAlgoAPI_Common.hxx> #include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Cut.hxx> #include <BRepAlgoAPI_Cut.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepClass3d_SolidClassifier.hxx> #include <BRepClass3d_SolidClassifier.hxx>
namespace IfcGeom { namespace IfcGeom {
@@ -38,6 +39,30 @@ namespace IfcGeom {
template <typename T> template <typename T>
class tree { class tree {
bool test(const TopoDS_Shape& A, const TopoDS_Shape& B, bool completely_within, double extend) const {
if (extend > 0.) {
BRepExtrema_DistShapeShape dss(A, B);
if (dss.Perform() && dss.NbSolution() >= 1) {
return dss.Value() <= extend;
}
} else if (completely_within) {
BRepAlgoAPI_Cut cut(B, A);
if (cut.IsDone()) {
if (IfcGeom::Kernel::count(cut.Shape(), TopAbs_SHELL) == 0) {
return true;
}
}
} else {
BRepAlgoAPI_Common common(A, B);
if (common.IsDone()) {
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
return true;
}
}
}
return false;
}
public: public:
void add(const T& t, const Bnd_Box& b) { void add(const T& t, const Bnd_Box& b) {
@@ -104,8 +129,8 @@ namespace IfcGeom {
} }
} }
std::vector<T> select(const T& t, bool completely_within = false) const { std::vector<T> select(const T& t, bool completely_within = false, double extend = 0.0) const {
std::vector<T> ts = select_box(t); std::vector<T> ts = select_box(t, completely_within, extend);
if (ts.empty()) { if (ts.empty()) {
return ts; return ts;
} }
@@ -126,29 +151,18 @@ namespace IfcGeom {
continue; continue;
} }
if (completely_within) { if (test(A, B, completely_within, extend)) {
BRepAlgoAPI_Cut cut(B, A); ts_filtered.push_back(*it);
if (cut.IsDone()) {
if (IfcGeom::Kernel::count(cut.Shape(), TopAbs_SHELL) == 0) {
ts_filtered.push_back(*it);
}
}
} else {
BRepAlgoAPI_Common common(A, B);
if (common.IsDone()) {
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
ts_filtered.push_back(*it);
}
}
} }
} }
return ts_filtered; return ts_filtered;
} }
std::vector<T> select(const TopoDS_Shape& s) const { std::vector<T> select(const TopoDS_Shape& s, bool completely_within = false, double extend = -1.e-5) const {
Bnd_Box bb; Bnd_Box bb;
BRepBndLib::AddClose(s, bb); BRepBndLib::AddClose(s, bb);
bb.SetGap(bb.GetGap() + extend);
std::vector<T> ts; std::vector<T> ts;
@@ -156,7 +170,7 @@ namespace IfcGeom {
return ts; return ts;
} }
ts = select_box(bb); ts = select_box(bb, completely_within);
if (ts.empty()) { if (ts.empty()) {
return ts; return ts;
@@ -168,16 +182,13 @@ namespace IfcGeom {
typename std::vector<T>::const_iterator it = ts.begin(); typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) { for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second; const TopoDS_Shape& B = shapes_.find(*it)->second;
if (IfcGeom::Kernel::count(B, TopAbs_SHELL) == 0) { if (IfcGeom::Kernel::count(B, TopAbs_SHELL) == 0) {
continue; continue;
} }
BRepAlgoAPI_Common common(s, B); if (test(s, B, completely_within, extend)) {
if (common.IsDone()) { ts_filtered.push_back(*it);
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
ts_filtered.push_back(*it);
}
} }
} }
@@ -258,6 +269,10 @@ namespace IfcGeom {
add_file(f, settings); add_file(f, settings);
} }
tree(IfcGeom::Iterator<double>& it) {
add_file(it);
}
void add_file(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) { void add_file(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) {
IfcGeom::IteratorSettings settings_ = settings; IfcGeom::IteratorSettings settings_ = settings;
settings_.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); settings_.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
@@ -266,10 +281,14 @@ namespace IfcGeom {
IfcGeom::Iterator<double> it(settings_, &f); IfcGeom::Iterator<double> it(settings_, &f);
add_file(it);
}
void add_file(IfcGeom::Iterator<double>& it) {
if (it.initialize()) { if (it.initialize()) {
do { do {
IfcGeom::BRepElement<double>* elem = (IfcGeom::BRepElement<double>*)it.get(); IfcGeom::BRepElement<double>* elem = (IfcGeom::BRepElement<double>*)it.get();
add((IfcUtil::IfcBaseEntity*)f.instance_by_id(elem->id()), elem->geometry().as_compound()); add((IfcUtil::IfcBaseEntity*)it.file()->instance_by_id(elem->id()), elem->geometry().as_compound());
} while (it.next()); } while (it.next());
} }
} }
@@ -0,0 +1,25 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_object": None,
"product": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["product"].Decomposes or []:
if not rel.is_a("IfcRelAggregates") or rel.RelatingObject != self.settings["relating_object"]:
continue
if len(rel.RelatedObjects) == 1:
return self.file.remove(rel)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["product"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
@@ -26,9 +26,9 @@ class Usecase:
controls = self.settings["relating_control"].Controls[0] controls = self.settings["relating_control"].Controls[0]
if controls: if controls:
related_objects = list(controls.RelatedObjects) related_objects = set(controls.RelatedObjects)
related_objects.append(self.settings["related_object"]) related_objects.add(self.settings["related_object"])
controls.RelatedObjects = related_objects controls.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls}) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls})
else: else:
controls = self.file.create_entity( controls = self.file.create_entity(
@@ -0,0 +1,22 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "ifc_class": "IfcQuantityCount"}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
quantity = self.file.create_entity(self.settings["ifc_class"], Name="Unnamed")
quantity[3] = 0.0
# This is a bold assumption
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
if self.settings["ifc_class"] == "IfcQuantityCount" and self.settings["cost_item"].Controls:
for rel in self.settings["cost_item"].Controls:
quantity[3] += len(rel.RelatedObjects)
quantities = list(self.settings["cost_item"].CostQuantities or [])
quantities.append(quantity)
self.settings["cost_item"].CostQuantities = quantities
return quantity
@@ -0,0 +1,16 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "ifc_class": "IfcQuantityCount"}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
value = self.file.create_entity("IfcCostValue")
values = list(self.settings["cost_item"].CostValues or [])
values.append(value)
self.settings["cost_item"].CostValues = values
return value
@@ -0,0 +1,32 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "qto_name": "", "prop_name": ""}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for control in self.settings["cost_item"].Controls or []:
for related_object in control.RelatedObjects:
self.add_quantity_from_related_object(related_object)
self.settings["cost_item"].CostQuantities = list(self.quantities)
def add_quantity_from_related_object(self, element):
if element.is_a("IfcTypeObject"):
for definition in element.HasPropertySets or []:
self.add_quantity_from_qto(definition)
else:
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
def add_quantity_from_qto(self, qto):
if not qto.is_a("IfcElementQuantity") or qto.Name.lower() != self.settings["qto_name"].lower():
return
for prop in qto.Quantities:
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
self.quantities.add(prop)
@@ -5,43 +5,133 @@ class Data:
is_loaded = False is_loaded = False
cost_schedules = {} cost_schedules = {}
cost_items = {} cost_items = {}
physical_quantities = {}
cost_values = {}
@classmethod @classmethod
def purge(cls): def purge(cls):
cls.is_loaded = False cls.is_loaded = False
cls.cost_schedules = {} cls.cost_schedules = {}
cls.cost_items = {} cls.cost_items = {}
cls.physical_quantities = {}
cls.cost_values = {}
@classmethod @classmethod
def load(cls, file): def load(cls, file):
cls.file = file
cls.cost_schedules = {} cls.cost_schedules = {}
cls.cost_items = {} cls.cost_items = {}
cls.physical_quantities = {}
cls.cost_values = {}
for cost_schedule in file.by_type("IfcCostSchedule"): for cost_schedule in cls.file.by_type("IfcCostSchedule"):
data = cost_schedule.get_info() data = cost_schedule.get_info()
del data["OwnerHistory"] del data["OwnerHistory"]
if data["SubmittedOn"]: if data["SubmittedOn"]:
data["SubmittedOn"] = ifcopenshell.util.date.ifc2datetime(data["SubmittedOn"]) data["SubmittedOn"] = ifcopenshell.util.date.ifc2datetime(data["SubmittedOn"])
if data["UpdateDate"]: if data["UpdateDate"]:
data["UpdateDate"] = ifcopenshell.util.date.ifc2datetime(data["UpdateDate"]) data["UpdateDate"] = ifcopenshell.util.date.ifc2datetime(data["UpdateDate"])
data["RelatedObjects"] = [] data["Controls"] = []
for rel in cost_schedule.Controls: for rel in cost_schedule.Controls:
for related_object in rel.RelatedObjects: for related_object in rel.RelatedObjects:
if related_object.is_a("IfcCostItem"): if related_object.is_a("IfcCostItem"):
data["RelatedObjects"].append(related_object.id()) data["Controls"].append(related_object.id())
break # We are only allowed one summary cost item break # We are only allowed one summary cost item
cls.cost_schedules[cost_schedule.id()] = data cls.cost_schedules[cost_schedule.id()] = data
for cost_item in file.by_type("IfcCostItem"): for cost_item in cls.file.by_type("IfcCostItem"):
data = cost_item.get_info() data = cost_item.get_info()
del data["OwnerHistory"] del data["OwnerHistory"]
del data["CostValues"] del data["CostValues"]
del data["CostQuantities"] data["IsNestedBy"] = []
data["RelatedObjects"] = []
data["Controls"] = [] data["Controls"] = []
for rel in cost_item.IsNestedBy: for rel in cost_item.IsNestedBy:
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")] [data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
for rel in cost_item.Controls: for rel in cost_item.Controls:
[data["Controls"].append(o.id()) for o in rel.RelatedObjects or []] [data["Controls"].append(o.id()) for o in rel.RelatedObjects or []]
cls.cost_items[cost_item.id()] = data cls.cost_items[cost_item.id()] = data
cls.is_loaded=True cls.load_cost_item_quantities(cost_item, data)
cls.load_cost_item_values(cost_item, data)
cls.is_loaded = True
@classmethod
def load_cost_item_quantities(cls, cost_item, data):
data["CostQuantities"] = []
data["TotalCostQuantity"] = cls.get_total_quantity(cost_item)
for quantity in cost_item.CostQuantities or []:
quantity_data = quantity.get_info()
del quantity_data["Unit"]
cls.physical_quantities[quantity.id()] = quantity_data
data["CostQuantities"].append(quantity.id())
@classmethod
def load_cost_item_values(cls, cost_item, data):
data["CostValues"] = []
data["TotalCostValue"] = 0.0
data["TotalAppliedValue"] = 0.0
for cost_value in cost_item.CostValues or []:
cls.load_cost_item_value(cost_item, cost_value)
data["CostValues"].append(cost_value.id())
data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"]
data["TotalCostValue"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
@classmethod
def load_cost_item_value(cls, cost_item, cost_value):
value_data = cost_value.get_info()
del value_data["AppliedValue"]
del value_data["UnitBasis"]
if value_data["ApplicableDate"]:
value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
if value_data["FixedUntilDate"]:
value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
value_data["Components"] = [c.id() for c in value_data["Components"] or []]
value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value)
cls.cost_values[cost_value.id()] = value_data
@classmethod
def calculate_applied_value(cls, cost_item, cost_value, category_filter=None):
result = 0
if cost_value.ArithmeticOperator and cost_value.Components:
pass # TODO
if cost_value.Category is None:
return cls.get_primitive_applied_value(cost_value.AppliedValue)
elif cost_value.Category == "*":
if cost_item.IsNestedBy:
return cls.sum_child_cost_items(cost_item)
else:
return cls.get_primitive_applied_value(cost_value.AppliedValue)
elif cost_value.Category:
if cost_item.IsNestedBy:
return cls.sum_child_cost_items(cost_item, category_filter=cost_value.Category)
else:
return cls.get_primitive_applied_value(cost_value.AppliedValue)
return result
@classmethod
def sum_child_cost_items(cls, cost_item, category_filter=None):
result = 0
for rel in cost_item.IsNestedBy:
for child_cost_item in rel.RelatedObjects:
for child_cost_value in child_cost_item.CostValues or []:
if category_filter and child_cost_value.Category != category_filter:
continue
child_applied_value = cls.calculate_applied_value(child_cost_item, child_cost_value)
child_quantity = cls.get_total_quantity(child_cost_item)
result += child_applied_value * child_quantity
return result
@classmethod
def get_total_quantity(cls, cost_item):
return sum([q[3] for q in cost_item.CostQuantities or []]) or 1.0
@classmethod
def get_primitive_applied_value(cls, applied_value):
if not applied_value:
return 0.0
elif isinstance(applied_value, float):
return applied_value
elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float):
return applied_value.wrappedValue
elif applied_value.is_a("IfcMeasureWithUnit"):
return applied_value.ValueComponent
assert False, "Applied value {applied_value} not implemented"
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"physical_quantity": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["physical_quantity"], name, value)
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_value": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types
value = self.file.createIfcReal(value)
setattr(self.settings["cost_value"], name, value)
@@ -0,0 +1,14 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "physical_quantity": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if len(self.file.get_inverse(self.settings["physical_quantity"])) == 1:
self.file.remove(self.settings["physical_quantity"])
return
quantities = list(self.settings["cost_item"].CostQuantities or [])
quantities.remove(self.settings["physical_quantity"])
self.settings["cost_item"].CostQuantities = quantities
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_value": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["cost_value"])
@@ -1,5 +1,4 @@
import ifcopenshell import ifcopenshell
import blenderbim.bim.schema # TODO: refactor
class Usecase: class Usecase:
@@ -1,5 +1,4 @@
import ifcopenshell import ifcopenshell
import blenderbim.bim.schema # TODO: refactor
class Usecase: class Usecase:
@@ -1,6 +1,6 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
import blenderbim.bim.schema # TODO: refactor elsewhere import ifcopenshell.util.pset
class Data: class Data:
@@ -17,6 +17,7 @@ class Data:
@classmethod @classmethod
def load(cls, file, product_id): def load(cls, file, product_id):
cls._file = file cls._file = file
cls._psetqto = ifcopenshell.util.pset.get_template("IFC4")
cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
if not file: if not file:
return return
@@ -150,7 +151,7 @@ class Data:
@classmethod @classmethod
def get_properties_from_template(cls, name): def get_properties_from_template(cls, name):
template = blenderbim.bim.schema.ifc.psetqto.get_by_name(name) template = cls._psetqto.get_by_name(name)
if not template: if not template:
return return
properties = [] properties = []
@@ -1,4 +1,4 @@
import blenderbim.bim.schema # TODO: refactor import ifcopenshell
class Usecase: class Usecase:
@@ -20,7 +20,9 @@ class Usecase:
self.settings["pset"].Name = self.settings["Name"] self.settings["pset"].Name = self.settings["Name"]
def load_pset_template(self): def load_pset_template(self):
self.pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["pset"].Name) # TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name)
def update_existing_properties(self): def update_existing_properties(self):
for prop in self.get_properties(): for prop in self.get_properties():
@@ -1,4 +1,4 @@
import blenderbim.bim.schema # TODO: refactor import ifcopenshell
class Usecase: class Usecase:
@@ -20,7 +20,9 @@ class Usecase:
self.settings["qto"].Name = self.settings["Name"] self.settings["qto"].Name = self.settings["Name"]
def load_qto_template(self): def load_qto_template(self):
self.qto_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["qto"].Name) # TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name)
def update_existing_properties(self): def update_existing_properties(self):
for prop in self.settings["qto"].Quantities or []: for prop in self.settings["qto"].Quantities or []:
@@ -1,6 +1,3 @@
import blenderbim.bim.schema # TODO: refactor
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -0,0 +1,35 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"parent_resource": None,
"ifc_class": "IfcCrewResource",
"name": None,
"predefined_type": "NOTDEFINED",
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
resource = ifcopenshell.api.run(
"root.create_entity",
self.file,
ifc_class=self.settings["ifc_class"],
predefined_type=self.settings["predefined_type"],
name=self.settings["name"],
)
# TODO: this is an ambiguity by buildingSMART: Can we nest an IfcCrewResource under an IfcCrewResource ?
# https://forums.buildingsmart.org/t/what-are-allowed-to-be-root-level-construction-resources/3550
if self.settings["parent_resource"]:
ifcopenshell.api.run(
"nest.assign_object", self.file, related_object=resource, relating_object=self.settings["parent_resource"]
)
else:
context = self.file.by_type("IfcContext")[0]
ifcopenshell.api.run(
"project.assign_declaration", self.file, definition=resource, relating_context=context
)
return resource
@@ -0,0 +1,43 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_resource": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["related_object"].HasAssignments:
for assignment in self.settings["related_object"].HasAssignments:
if (
assignment.is_a("IfclRelAssignsToResource")
and assignment.RelatingResource == self.settings["relating_resource"]
):
return
resource_of = None
if self.settings["relating_resource"].ResourceOf:
resource_of = self.settings["relating_resource"].ResourceOf[0]
if resource_of:
related_objects = list(resource_of.RelatedObjects)
related_objects.append(self.settings["related_object"])
resource_of.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": resource_of})
else:
resource_of = self.file.create_entity(
"IfcRelAssignsToResource",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["related_object"]],
"RelatingResource": self.settings["relating_resource"],
}
)
return resource_of
@@ -0,0 +1,23 @@
class Data:
is_loaded = False
resources = {}
@classmethod
def purge(cls):
cls.resources = {}
@classmethod
def load(cls, file):
cls.resources = {}
for resource in file.by_type("IfcResource"):
data = resource.get_info()
del data["OwnerHistory"]
data["IsNestedBy"] = []
for rel in resource.IsNestedBy:
[data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects]
data["ResourceOf"] = []
for rel in resource.ResourceOf:
[data["ResourceOf"].append(o.id()) for o in rel.RelatedObjects]
data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None
cls.resources[resource.id()] = data
cls.is_loaded=True
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"resource": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["resource"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"resource": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
# TODO: do a deep purge
self.file.remove(self.settings["resource"])
@@ -0,0 +1,24 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_resource": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToResource") or rel.RelatingResource != self.settings["relating_resource"]:
continue
if len(rel.RelatedObjects) == 1:
return self.file.remove(rel)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["related_object"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
@@ -0,0 +1,25 @@
import ifcopenshell.api
import ifcopenshell.util.date
from datetime import datetime
from datetime import timedelta
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"recurrence_pattern": None,
"start_time": None,
"end_time": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
time_period = self.file.create_entity("IfcTimePeriod")
time_period.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcTime")
time_period.EndTime = ifcopenshell.util.date.datetime2ifc(self.settings["end_time"], "IfcTime")
time_periods = list(self.settings["recurrence_pattern"].TimePeriods or [])
time_periods.append(time_period)
self.settings["recurrence_pattern"].TimePeriods = time_periods
return time_period
@@ -1,17 +1,17 @@
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {"work_calendar": None, "type": "WorkingTimes", "name": None} self.settings = {"work_calendar": None, "time_type": None}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
work_time = self.file.create_entity("IfcWorkTime", **{"Name": self.settings["name"]}) work_time = self.file.create_entity("IfcWorkTime")
if self.settings["type"] == "WorkingTimes": if self.settings["time_type"] == "WorkingTimes":
working_times = list(self.settings["work_calendar"].WorkingTimes or []) working_times = list(self.settings["work_calendar"].WorkingTimes or [])
working_times.append(work_time) working_times.append(work_time)
self.settings["work_calendar"].WorkingTimes = working_times self.settings["work_calendar"].WorkingTimes = working_times
elif self.settings["type"] == "ExceptionTimes": elif self.settings["time_type"] == "ExceptionTimes":
exception_times = list(self.settings["work_calendar"].ExceptionTimes or []) exception_times = list(self.settings["work_calendar"].ExceptionTimes or [])
exception_times.append(work_time) exception_times.append(work_time)
self.settings["work_calendar"].ExceptionTimes = exception_times self.settings["work_calendar"].ExceptionTimes = exception_times
@@ -8,8 +8,11 @@ class Usecase:
def execute(self): def execute(self):
recurrence = self.file.createIfcRecurrencePattern(self.settings["recurrence_type"]) recurrence = self.file.createIfcRecurrencePattern(self.settings["recurrence_type"])
if self.settings["parent"].is_a("IfcWorkTime") and self.settings["parent"].RecurrencePattern: if self.settings["parent"].is_a("IfcWorkTime"):
if len(self.file.get_inverse(self.settings["parent"].RecurrencePattern)) == 1: if (
self.settings["parent"].RecurrencePattern
and len(self.file.get_inverse(self.settings["parent"].RecurrencePattern)) == 1
):
self.file.remove(self.settings["parent"].RecurrencePattern) self.file.remove(self.settings["parent"].RecurrencePattern)
self.settings["parent"].RecurrencePattern = recurrence self.settings["parent"].RecurrencePattern = recurrence
elif self.settings["parent"].is_a("IfcTaskTimeRecurring"): elif self.settings["parent"].is_a("IfcTaskTimeRecurring"):
@@ -5,6 +5,10 @@ class Data:
is_loaded = False is_loaded = False
work_plans = {} work_plans = {}
work_schedules = {} work_schedules = {}
work_calendars = {}
work_times = {}
recurrence_patterns = {}
time_periods = {}
tasks = {} tasks = {}
task_times = {} task_times = {}
@@ -14,6 +18,9 @@ class Data:
cls.work_plans = {} cls.work_plans = {}
cls.work_schedules = {} cls.work_schedules = {}
cls.work_calendars = {} cls.work_calendars = {}
cls.work_times = {}
cls.recurrence_patterns = {}
cls.time_periods = {}
cls.tasks = {} cls.tasks = {}
cls.task_times = {} cls.task_times = {}
@@ -25,6 +32,9 @@ class Data:
cls.load_work_plans() cls.load_work_plans()
cls.load_work_schedules() cls.load_work_schedules()
cls.load_work_calendars() cls.load_work_calendars()
cls.load_work_times()
cls.load_recurrence_patterns()
cls.load_time_periods()
cls.load_tasks() cls.load_tasks()
cls.load_task_times() cls.load_task_times()
cls.is_loaded = True cls.is_loaded = True
@@ -41,6 +51,9 @@ class Data:
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"]) data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
if data["FinishTime"]: if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]) data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
data["IsDecomposedBy"] = []
for rel in work_plan.IsDecomposedBy:
data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects])
cls.work_plans[work_plan.id()] = data cls.work_plans[work_plan.id()] = data
@classmethod @classmethod
@@ -68,10 +81,37 @@ class Data:
for work_calendar in cls._file.by_type("IfcWorkCalendar"): for work_calendar in cls._file.by_type("IfcWorkCalendar"):
data = work_calendar.get_info() data = work_calendar.get_info()
del data["OwnerHistory"] del data["OwnerHistory"]
del data["WorkingTimes"] data["WorkingTimes"] = [t.id() for t in work_calendar.WorkingTimes or []]
del data["ExceptionTimes"] data["ExceptionTimes"] = [t.id() for t in work_calendar.ExceptionTimes or []]
cls.work_calendars[work_calendar.id()] = data cls.work_calendars[work_calendar.id()] = data
@classmethod
def load_work_times(cls):
cls.work_times = {}
for work_time in cls._file.by_type("IfcWorkTime"):
data = work_time.get_info()
data["Start"] = ifcopenshell.util.date.ifc2datetime(data["Start"]) if data["Start"] else None
data["Finish"] = ifcopenshell.util.date.ifc2datetime(data["Finish"]) if data["Finish"] else None
data["RecurrencePattern"] = work_time.RecurrencePattern.id() if work_time.RecurrencePattern else None
cls.work_times[work_time.id()] = data
@classmethod
def load_recurrence_patterns(cls):
cls.recurrence_patterns = {}
for recurrence_pattern in cls._file.by_type("IfcRecurrencePattern"):
data = recurrence_pattern.get_info()
data["TimePeriods"] = [t.id() for t in recurrence_pattern.TimePeriods or []]
cls.recurrence_patterns[recurrence_pattern.id()] = data
@classmethod
def load_time_periods(cls):
cls.time_periods = {}
for time_period in cls._file.by_type("IfcTimePeriod"):
cls.time_periods[time_period.id()] = {
"StartTime": ifcopenshell.util.date.ifc2datetime(time_period.StartTime),
"EndTime": ifcopenshell.util.date.ifc2datetime(time_period.EndTime),
}
@classmethod @classmethod
def load_tasks(cls): def load_tasks(cls):
cls.tasks = {} cls.tasks = {}
@@ -13,9 +13,14 @@ class Usecase:
if name == "TimePeriods" and value: if name == "TimePeriods" and value:
periods = [] periods = []
for period in value: for period in value:
periods.append(self.file.create_entity("IfcTimePeriod", **{ periods.append(
"StartTime": ifcopenshell.util.date.datetime2ifc(period[0]), self.file.create_entity(
"EndTime": ifcopenshell.util.date.datetime2ifc(period[1]) "IfcTimePeriod",
})) **{
"StartTime": ifcopenshell.util.date.datetime2ifc(period[0]),
"EndTime": ifcopenshell.util.date.datetime2ifc(period[1]),
},
)
)
value = periods value = periods
setattr(self.settings["recurrence_pattern"], name, value) setattr(self.settings["recurrence_pattern"], name, value)
@@ -10,6 +10,6 @@ class Usecase:
def execute(self): def execute(self):
for name, value in self.settings["attributes"].items(): for name, value in self.settings["attributes"].items():
if name in ["Start", "Finish"]: if value and name in ["Start", "Finish"]:
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate")
setattr(self.settings["work_time"], name, value) setattr(self.settings["work_time"], name, value)
@@ -0,0 +1,12 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"time_period": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["time_period"])
@@ -1,3 +1,6 @@
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -1,3 +1,6 @@
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"work_time": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["work_time"])
@@ -0,0 +1,12 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"recurrence_pattern": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["recurrence_pattern"])
@@ -134,6 +134,9 @@ class tree(ifcopenshell_wrapper.tree):
def add_file(self, file, settings): def add_file(self, file, settings):
ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings) ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings)
def add_iterator(self, iterator):
ifcopenshell_wrapper.tree.add_file(self, iterator)
def select(self, value, **kwargs): def select(self, value, **kwargs):
def unwrap(value): def unwrap(value):
@@ -79,6 +79,7 @@ class entity(facet):
""" """
The IDS entity facet currently *with* inheritance The IDS entity facet currently *with* inheritance
""" """
parameters = ["name", "predefinedtype"] parameters = ["name", "predefinedtype"]
def __call__(self, inst, logger): def __call__(self, inst, logger):
@@ -1,5 +1,5 @@
import datetime
from re import findall from re import findall
from datetime import datetime
def duration2dict(duration): def duration2dict(duration):
@@ -12,12 +12,16 @@ def duration2dict(duration):
def ifc2datetime(element): def ifc2datetime(element):
if isinstance(element, str) and element[0] == "P": # IfcDuration if isinstance(element, str) and element[0] == "P": # IfcDuration
return duration2dict(element) return duration2dict(element)
elif isinstance(element, str): # IfcDateTime, IfcDate elif isinstance(element, str) and element[2] == ":": # IfcTime
return datetime.fromisoformat(element) return datetime.time.fromisoformat(element)
elif isinstance(element, str) and ":" in element: # IfcDateTime
return datetime.datetime.fromisoformat(element)
elif isinstance(element, str): # IfcDate
return datetime.date.fromisoformat(element)
elif isinstance(element, int): # IfcTimeStamp elif isinstance(element, int): # IfcTimeStamp
return datetime.fromtimestamp(element) return datetime.datetime.fromtimestamp(element)
elif element.is_a("IfcDateAndTime"): elif element.is_a("IfcDateAndTime"):
return datetime( return datetime.datetime(
element.DateComponent.YearComponent, element.DateComponent.YearComponent,
element.DateComponent.MonthComponent, element.DateComponent.MonthComponent,
element.DateComponent.DayComponent, element.DateComponent.DayComponent,
@@ -27,7 +31,7 @@ def ifc2datetime(element):
# TODO: implement TimeComponent timezone # TODO: implement TimeComponent timezone
) )
elif element.is_a("IfcCalendarDate"): elif element.is_a("IfcCalendarDate"):
return datetime( return datetime.date(
element.YearComponent, element.YearComponent,
element.MonthComponent, element.MonthComponent,
element.DayComponent, element.DayComponent,
@@ -36,15 +40,24 @@ def ifc2datetime(element):
def datetime2ifc(dt, ifc_type): def datetime2ifc(dt, ifc_type):
if isinstance(dt, str): if isinstance(dt, str):
dt = datetime.fromisoformat(dt) dt = datetime.datetime.fromisoformat(dt)
if ifc_type == "IfcTimeStamp": if ifc_type == "IfcTimeStamp":
return int(dt.timestamp()) return int(dt.timestamp())
elif ifc_type == "IfcDateTime": elif ifc_type == "IfcDateTime":
return dt.isoformat() if isinstance(dt, datetime.datetime):
return dt.isoformat()
elif isinstance(dt, datetime.date):
return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat()
elif ifc_type == "IfcDate": elif ifc_type == "IfcDate":
return dt.date().isoformat() if isinstance(dt, datetime.datetime):
return dt.date().isoformat()
elif isinstance(dt, datetime.date):
return dt.isoformat()
elif ifc_type == "IfcTime": elif ifc_type == "IfcTime":
return dt.time().isoformat() if isinstance(dt, datetime.datetime):
return dt.time().isoformat()
elif isinstance(dt, datetime.time):
return dt.isoformat()
elif ifc_type == "IfcCalendarDate": elif ifc_type == "IfcCalendarDate":
return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year} return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year}
elif ifc_type == "IfcLocalTime": elif ifc_type == "IfcLocalTime":
@@ -6,6 +6,15 @@ from typing import List, Generator, Optional
import ifcopenshell import ifcopenshell
from ifcopenshell.entity_instance import entity_instance from ifcopenshell.entity_instance import entity_instance
templates = {}
def get_template(schema):
global templates
if schema not in templates:
templates[schema] = PsetQto(schema)
return templates[schema]
class PsetQto: class PsetQto:
templates_path = { templates_path = {
+34 -13
View File
@@ -12,7 +12,8 @@ class P62Ifc:
self.work_plan = None self.work_plan = None
self.project = {} self.project = {}
self.wbs = {} self.wbs = {}
self.activity = {} self.activities = {}
self.relationships = {}
def execute(self): def execute(self):
self.parse_xml() self.parse_xml()
@@ -36,16 +37,26 @@ class P62Ifc:
} }
for activity in project.findall("pr:Activity", ns): for activity in project.findall("pr:Activity", ns):
self.wbs[activity.find("pr:WBSObjectId", ns).text]["activities"].append( activity_id = activity.find("pr:ObjectId", ns).text
{ wbs_id = activity.find("pr:WBSObjectId", ns).text
"Name": activity.find("pr:Name", ns).text, if not wbs_id:
"Identification": activity.find("pr:Id", ns).text, print("No WBS ID found for activity", activity_id)
"StartDate": datetime.fromisoformat(activity.find("pr:StartDate", ns).text), continue
"FinishDate": datetime.fromisoformat(activity.find("pr:FinishDate", ns).text), self.wbs[wbs_id]["activities"].append(activity_id)
"Status": activity.find("pr:Status", ns).text, self.activities[activity_id] = {
"ifc": None, "Name": activity.find("pr:Name", ns).text,
} "Identification": activity.find("pr:Id", ns).text,
) "StartDate": datetime.fromisoformat(activity.find("pr:StartDate", ns).text),
"FinishDate": datetime.fromisoformat(activity.find("pr:FinishDate", ns).text),
"Status": activity.find("pr:Status", ns).text,
"ifc": None,
}
for relationship in project.findall("pr:Relationship", ns):
self.relationships[relationship.find("pr:ObjectId", ns).text] = {
"PredecessorActivity": relationship.find("pr:PredecessorActivityObjectId", ns).text,
"SuccessorActivity": relationship.find("pr:SuccessorActivityObjectId", ns).text,
}
def get_wbs(self, wbs): def get_wbs(self, wbs):
return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []} return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []}
@@ -55,6 +66,7 @@ class P62Ifc:
self.file = self.create_boilerplate_ifc() self.file = self.create_boilerplate_ifc()
work_schedule = self.create_work_schedule() work_schedule = self.create_work_schedule()
self.create_tasks(work_schedule) self.create_tasks(work_schedule)
self.create_rel_sequences()
def create_work_schedule(self): def create_work_schedule(self):
return ifcopenshell.api.run( return ifcopenshell.api.run(
@@ -78,8 +90,8 @@ class P62Ifc:
task=wbs["ifc"], task=wbs["ifc"],
attributes={"Name": wbs["Name"], "Identification": wbs["Code"]}, attributes={"Name": wbs["Name"], "Identification": wbs["Code"]},
) )
for activity in wbs["activities"]: for activity_id in wbs["activities"]:
self.create_task_from_activity(activity, wbs, work_schedule) self.create_task_from_activity(self.activities[activity_id], wbs, work_schedule)
def create_task_from_activity(self, activity, wbs, work_schedule): def create_task_from_activity(self, activity, wbs, work_schedule):
activity["ifc"] = ifcopenshell.api.run( activity["ifc"] = ifcopenshell.api.run(
@@ -109,6 +121,15 @@ class P62Ifc:
}, },
) )
def create_rel_sequences(self):
for relationship in self.relationships.values():
ifcopenshell.api.run(
"sequence.assign_sequence",
self.file,
relating_process=self.activities[relationship["PredecessorActivity"]]["ifc"],
related_process=self.activities[relationship["SuccessorActivity"]]["ifc"],
)
def create_boilerplate_ifc(self): def create_boilerplate_ifc(self):
self.file = ifcopenshell.file(schema="IFC4") self.file = ifcopenshell.file(schema="IFC4")
self.work_plan = self.file.create_entity("IfcWorkPlan") self.work_plan = self.file.create_entity("IfcWorkPlan")
+1
View File
@@ -45,6 +45,7 @@ namespace IfcUtil {
IFC_PARSE_API const char* ArgumentTypeToString(ArgumentType argument_type); IFC_PARSE_API const char* ArgumentTypeToString(ArgumentType argument_type);
/// Returns false when the string `s` contains character outside of {'0', '1'}
IFC_PARSE_API bool valid_binary_string(const std::string& s); IFC_PARSE_API bool valid_binary_string(const std::string& s);
} }
+1 -1
View File
@@ -317,7 +317,7 @@ IfcCharacterEncoder::operator std::string() {
// Either 2 or 4 to uses \X2 or \X4 respectively. // Either 2 or 4 to uses \X2 or \X4 respectively.
// Currently hardcoded to 4, but \X2 might be // Currently hardcoded to 4, but \X2 might be
// sufficient for nearly all purposes. // sufficient for nearly all purposes.
const int num_bytes = (str.empty() || *std::max_element(str.begin(), str.end())) > 0xffff ? 4 : 2; const int num_bytes = (str.empty() || (*std::max_element(str.begin(), str.end()) > 0xffff)) ? 4 : 2;
const std::string num_bytes_str = std::string(1,num_bytes + 0x30); const std::string num_bytes_str = std::string(1,num_bytes + 0x30);
bool in_extended = false; bool in_extended = false;
+7
View File
@@ -484,6 +484,12 @@ Ifc4x3_rc1::IfcStyledItem* create_styled_item(Ifc4x3_rc1::IfcRepresentationItem*
return new Ifc4x3_rc1::IfcStyledItem(item, style_assignments, boost::none); return new Ifc4x3_rc1::IfcStyledItem(item, style_assignments, boost::none);
} }
Ifc4x3_rc2::IfcStyledItem* create_styled_item(Ifc4x3_rc2::IfcRepresentationItem* item, Ifc4x3_rc2::IfcPresentationStyleAssignment* style_assignment) {
IfcEntityList::ptr style_assignments(new IfcEntityList);
style_assignments->push(style_assignment);
return new Ifc4x3_rc2::IfcStyledItem(item, style_assignments, boost::none);
}
template <typename Schema> template <typename Schema>
void IfcHierarchyHelper<Schema>::setSurfaceColour(typename Schema::IfcRepresentation* rep, void IfcHierarchyHelper<Schema>::setSurfaceColour(typename Schema::IfcRepresentation* rep,
typename Schema::IfcPresentationStyleAssignment* style_assignment) typename Schema::IfcPresentationStyleAssignment* style_assignment)
@@ -581,3 +587,4 @@ template IFC_PARSE_API class IfcHierarchyHelper<Ifc4>;
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x1>; template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x1>;
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x2>; template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x2>;
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x3_rc1>; template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x3_rc1>;
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x3_rc2>;
+1
View File
@@ -37,6 +37,7 @@
#include "../ifcparse/Ifc4x1.h" #include "../ifcparse/Ifc4x1.h"
#include "../ifcparse/Ifc4x2.h" #include "../ifcparse/Ifc4x2.h"
#include "../ifcparse/Ifc4x3_rc1.h" #include "../ifcparse/Ifc4x3_rc1.h"
#include "../ifcparse/Ifc4x3_rc2.h"
#include "../ifcparse/IfcFile.h" #include "../ifcparse/IfcFile.h"
+1 -1
View File
@@ -178,7 +178,7 @@ namespace IfcParse {
public: public:
ArgumentList() : size_(0), list_(0) {} ArgumentList() : size_(0), list_(0) {}
ArgumentList(size_t n) : size_(n), list_(new Argument*[size_]) {} ArgumentList(size_t n) : size_(n), list_(new Argument*[size_] {0}) {}
~ArgumentList(); ~ArgumentList();
void read(IfcSpfLexer* t, std::vector<unsigned int>& ids); void read(IfcSpfLexer* t, std::vector<unsigned int>& ids);
+1 -1
View File
@@ -17,7 +17,7 @@ def execute(args, is_library=None):
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"]) patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
print("# Patching ...") print("# Patching ...")
patcher.patch() patcher.patch()
ifc_file = patcher.file ifc_file = getattr(patcher, "file_patched", patcher.file)
if is_library is True: if is_library is True:
return ifc_file return ifc_file
print("# Writing patched file ...") print("# Writing patched file ...")
+3 -3
View File
@@ -10,9 +10,9 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
self.new = ifcopenshell.file(schema=self.args[0]) self.file_patched = ifcopenshell.file(schema=self.args[0])
migrator = ifcopenshell.util.schema.Migrator() migrator = ifcopenshell.util.schema.Migrator()
for element in self.file: for element in self.file:
migrator.migrate(element, self.file_patched)
print("Migrating", element) print("Migrating", element)
print("Successfully converted to", migrator.migrate(element, self.new)) print("Successfully converted to", migrator.migrate(element, self.file_patched))
self.file = self.new
+123 -80
View File
@@ -574,6 +574,11 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
b->second[0] - b->first[0], b->second[0] - b->first[0],
b->second[1] - b->first[1] b->second[1] - b->first[1]
); );
#if OCC_VERSION_HEX >= 0x70300
view_box_3d_.emplace();
BRepBndLib::AddOBB(compound_unmirrored, *view_box_3d_, false, false, false);
#endif
} }
std::vector<string_property> props; std::vector<string_property> props;
@@ -712,6 +717,17 @@ void SvgSerializer::write(const geometry_data& data) {
// (When determinant < 0, copy is implied and the input is not mutated.) // (When determinant < 0, copy is implied and the input is not mutated.)
auto compound_unmirrored = make_transform_global.Shape(); auto compound_unmirrored = make_transform_global.Shape();
#if OCC_VERSION_HEX >= 0x70300
if (view_box_3d_) {
Bnd_OBB obb;
BRepBndLib::AddOBB(compound_unmirrored, obb, false, false, false);
if (view_box_3d_->IsOut(obb)) {
Logger::Notice("Not including element due to viewBox", data.product);
return;
}
}
#endif
if (is_floor_plan_) { if (is_floor_plan_) {
BRepBndLib::Add(compound_unmirrored, bnd_); BRepBndLib::Add(compound_unmirrored, bnd_);
} }
@@ -1040,10 +1056,15 @@ void SvgSerializer::write(const geometry_data& data) {
object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end()); object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end());
} }
auto z_local = gp::DZ().Transformed(data.trsf.Inverted());
if (data.product->declaration().is("IfcAnnotation") && // is an Annotation if (data.product->declaration().is("IfcAnnotation") && // is an Annotation
(proj.Magnitude() > 1.e-5) && // when projected onto the view has a length (proj.Magnitude() > 1.e-5) && // when projected onto the view has a length
zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey, is_floor_plan_
// this excludes the upper bound with a small tolerance ? (zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey,
// this excludes the upper bound with a small tolerance
: (projection_direction.Dot(z_local) < -0.99) // For elevations only include annotations that are "facing" the view direction
)
{ {
auto svg_name = data.svg_name; auto svg_name = data.svg_name;
@@ -1060,9 +1081,24 @@ void SvgSerializer::write(const geometry_data& data) {
} }
} }
auto subshape_to_use = subshape;
if (variant.which() == 2) {
// @todo remove duplication with code below.
gp_Trsf trsf;
trsf.SetTransformation(gp::XOY(), pln.Position());
subshape_to_use.Move(trsf);
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(subshape_to_use, trsf_mirror, true);
make_transform_mirror.Build();
subshape_to_use = make_transform_mirror.Shape();
}
if (object_type == "Dimension") { if (object_type == "Dimension") {
TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE); TopExp_Explorer exp(subshape_to_use, TopAbs_EDGE, TopAbs_FACE);
for (; exp.More(); exp.Next()) { for (; exp.More(); exp.Next()) {
const auto& e = TopoDS::Edge(exp.Current()); const auto& e = TopoDS::Edge(exp.Current());
TopoDS_Vertex v0, v1; TopoDS_Vertex v0, v1;
@@ -1135,7 +1171,7 @@ void SvgSerializer::write(const geometry_data& data) {
} else if (object_type == "Symbol") { } else if (object_type == "Symbol") {
TopExp_Explorer exp(subshape, TopAbs_WIRE, TopAbs_FACE); TopExp_Explorer exp(subshape_to_use, TopAbs_WIRE, TopAbs_FACE);
for (; exp.More(); exp.Next()) { for (; exp.More(); exp.Next()) {
const auto& W = TopoDS::Wire(exp.Current()); const auto& W = TopoDS::Wire(exp.Current());
write(*po, W, *dash_it); write(*po, W, *dash_it);
@@ -1649,87 +1685,92 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
gp_Trsf trsf; gp_Trsf trsf;
if (kernel.convert_placement(*pl, trsf)) { if (kernel.convert_placement(*pl, trsf)) {
auto v = trsf.TranslationPart(); auto v = gp_Pnt(trsf.TranslationPart());
if (k.first) {
v.ChangeCoord(1) *= -1.;
trsf.SetTranslationPart(v);
}
if (!range || (v.Z() >= range->first && v.Z() < range->second)) { auto z_local = gp::DZ().Transformed(trsf);
auto view_dir = z_local.Dot(meta.pln_3d.Axis().Direction());
if (meta.pln_3d.Position().Direction().Dot(gp_Dir(trsf.HVectorialPart().Column(3))) > 0.99) { if ((!range || (v.Z() >= range->first && v.Z() < range->second)) && view_dir > 0.99) {
auto svg_name = nameElement(ann);
path_object* po;
if (k.first) {
po = &start_path(meta.pln_3d, k.first, svg_name);
}
else {
po = &start_path(meta.pln_3d, k.second, svg_name);
}
if (object_type.size()) { gp_Trsf trsf_view;
// postfix the object_type for CSS matching trsf_view.SetTransformation(gp::XOY(), meta.pln_3d.Position());
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\""); v.Transform(trsf_view);
}
boost::optional<double> font_size; auto svg_name = nameElement(ann);
std::vector<std::string> tokens; path_object* po;
boost::split(tokens, name, boost::is_any_of("_")); if (k.first) {
if (tokens.size() == 2) { po = &start_path(meta.pln_3d, k.first, svg_name);
try { } else {
font_size = boost::lexical_cast<double>(tokens.back()); po = &start_path(meta.pln_3d, k.second, svg_name);
}
catch (...) {}
}
// @todo column or row?
double z_rotation = gp_Dir(trsf.HVectorialPart().Column(1)).AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
z_rotation *= 180. / M_PI;
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" <text text-anchor=\"left\" x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" y=\"");
ycoords.push_back(path.add(v.Y()));
path.add("\" transform=\"rotate(");
path.add(z_rotation);
path.add(" ");
xcoords.push_back(path.add(v.X()));
path.add(" ");
ycoords.push_back(path.add(v.Y()));
path.add(")\"");
if (font_size) {
path.add(" font-size=\"");
path.add(*font_size);
path.add("\"");
}
path.add(">");
std::vector<std::string> labels{ desc };
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
const auto& l = *lit;
double dy = labels.begin() == lit
? 0.0 // align bottom
: 1.0; // <- dy is relative to the previous text element, so
// always 1 for successive spans.
path.add("<tspan x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" dy=\"");
path.add(boost::lexical_cast<std::string>(dy));
path.add("em\">");
path.add(l);
path.add("</tspan>");
}
path.add("</text>");
po->second.push_back(path);
} }
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
boost::optional<double> font_size;
std::vector<std::string> tokens;
boost::split(tokens, name, boost::is_any_of("_"));
if (tokens.size() == 2) {
try {
font_size = boost::lexical_cast<double>(tokens.back());
}
catch (...) {}
}
// @todo column or row?
double z_rotation = gp::DX().Transformed(trsf).AngleWithRef(
meta.pln_3d.Position().XDirection(),
meta.pln_3d.Position().Direction()
);
z_rotation *= 180. / M_PI;
auto y = -v.Y();
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" <text text-anchor=\"left\" x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" y=\"");
ycoords.push_back(path.add(y));
path.add("\" transform=\"rotate(");
path.add(z_rotation);
path.add(" ");
xcoords.push_back(path.add(v.X()));
path.add(" ");
ycoords.push_back(path.add(y));
path.add(")\"");
if (font_size) {
path.add(" font-size=\"");
path.add(*font_size);
path.add("\"");
}
path.add(">");
std::vector<std::string> labels{ desc };
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
const auto& l = *lit;
double dy = labels.begin() == lit
? 0.0 // align bottom
: 1.0; // <- dy is relative to the previous text element, so
// always 1 for successive spans.
path.add("<tspan x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" dy=\"");
path.add(boost::lexical_cast<std::string>(dy));
path.add("em\">");
path.add(l);
path.add("</tspan>");
}
path.add("</text>");
po->second.push_back(path);
} }
} }
} }
@@ -1848,6 +1889,8 @@ void SvgSerializer::finalize() {
draw_hlr(ax, { nullptr, drawing_name }); draw_hlr(ax, { nullptr, drawing_name });
} }
addTextAnnotations({ nullptr, drawing_name });
if (storey_height_display_ != SH_NONE && pln && std::abs(pln->Position().Direction().Z()) < 1.e-5) { if (storey_height_display_ != SH_NONE && pln && std::abs(pln->Position().Direction().Z()) < 1.e-5) {
auto storeys = this->file->instances_by_type("IfcBuildingStorey"); auto storeys = this->file->instances_by_type("IfcBuildingStorey");
if (storeys) { if (storeys) {
+10
View File
@@ -33,6 +33,11 @@
#include <HLRAlgo_Projector.hxx> #include <HLRAlgo_Projector.hxx>
#include <gp_Pln.hxx> #include <gp_Pln.hxx>
#include <Bnd_Box.hxx> #include <Bnd_Box.hxx>
#include <Standard_Version.hxx>
#if OCC_VERSION_HEX >= 0x70300
#include <Bnd_OBB.hxx>
#endif
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -141,6 +146,11 @@ protected:
boost::optional<std::pair<double, double>> size_, offset_2d_; boost::optional<std::pair<double, double>> size_, offset_2d_;
boost::optional<std::string> space_name_transform_; boost::optional<std::string> space_name_transform_;
#if OCC_VERSION_HEX >= 0x70300
boost::optional<Bnd_OBB> view_box_3d_;
#endif
bool with_section_heights_from_storey_, print_space_names_, print_space_areas_; bool with_section_heights_from_storey_, print_space_names_, print_space_areas_;
storey_height_display_types storey_height_display_; storey_height_display_types storey_height_display_;
bool draw_door_arcs_, is_floor_plan_; bool draw_door_arcs_, is_floor_plan_;