merge master v0.6.0

This commit is contained in:
admin
2021-05-06 09:54:33 +08:00
parent 397b6ae5fe
commit db5432d902
76 changed files with 3455 additions and 818 deletions
+5
View File
@@ -1,4 +1,7 @@
# Dependency and build folders created by the build scripts
/_build-vs2017-x64/
/_deps-vs2017-x64-installed/
/_deps/
/deps*/
/build*/
/install*/
@@ -23,3 +26,5 @@ __pycache__
*.mo
# Vim
*.swp
Binary file not shown.
+3 -3
View File
@@ -19,8 +19,8 @@ Prerequisites
Dependencies
-------------
* [Boost](http://www.boost.org/)
* [Open Cascade](http://opencascade.org) - *optional*, but required for building IfcGeom
([official](http://www.opencascade.org/getocc/download/loadocc/), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
* [Open Cascade](https://dev.opencascade.org/) - *optional*, but required for building IfcGeom
([official](https://dev.opencascade.org/release), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
For converting IFC representation items into BRep solids and tesselated meshes
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
For IfcConvert to be able to write tessellated Collada (.dae) files
@@ -101,7 +101,7 @@ Note: where `make -j` is written, add a number roughly equal to the amount of CP
$ make -j
$ sudo make install
**2c)** or obtain and compile OCCT from http://www.opencascade.org/getocc/download/loadocc/
**2c)** or obtain and compile OCCT from https://dev.opencascade.org/release
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
+21
View File
@@ -177,6 +177,27 @@ endif
cp -r dist/working/svgwrite-1.3.1/svgwrite dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides fuzzy date parsing for construction sequencing
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz
cd dist/working && tar -xzvf python-dateutil*
cp -r dist/working/python-dateutil-2.8.1/dateutil dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides duration parsing for construction sequencing
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz
cd dist/working && tar -xzvf isodate*
cp -r dist/working/isodate-0.6.0/src/isodate dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
mkdir dist/working
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
cp dist/working/jsgantt* dist/blenderbim/bim/data/gantt/
rm -rf dist/working
# Required by IFCDiff
mkdir dist/working
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
+2 -3
View File
@@ -24,8 +24,9 @@ if bpy is not None:
"aggregate": None,
"geometry": None,
"cobie": None,
"sequence": None,
"resource": None,
"cost": None,
"sequence": None,
"group": None,
"structural": None,
"material": None,
@@ -173,7 +174,6 @@ if bpy is not None:
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
bpy.app.handlers.save_pre.append(handler.storeIdMap)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
@@ -200,7 +200,6 @@ if bpy is not None:
bpy.utils.unregister_class(cls)
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
bpy.app.handlers.save_pre.remove(handler.storeIdMap)
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+45 -14
View File
@@ -8,6 +8,7 @@ import tempfile
import ifcopenshell
import ifcopenshell.util.placement
import ifcopenshell.api
from ifcopenshell.api.spatial.data import Data as SpatialData
from blenderbim.bim.ifc import IfcStore
import addon_utils
@@ -46,9 +47,6 @@ class IfcExporter:
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
with open(self.ifc_export_settings.output_file, "w") as outfile:
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
if bpy.context.scene.BIMProjectProperties.is_authoring:
if bpy.data.filepath:
bpy.ops.wm.save_mainfile()
def set_header(self):
# TODO: add all metadata, pending bug #747
@@ -79,16 +77,19 @@ class IfcExporter:
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
to_delete = []
for guid, obj in IfcStore.guid_map.items():
for ifc_definition_id, obj in IfcStore.id_map.items():
try:
self.sync_object_placement(obj)
except:
pass
if self.should_delete(guid, obj):
to_delete.append(guid)
self.sync_object_container(ifc_definition_id, obj)
except ReferenceError:
pass # The object is likely deleted
if self.should_delete(obj):
to_delete.append(ifc_definition_id)
for guid in to_delete:
product = self.file.by_id(guid)
SpatialData.purge()
for ifc_definition_id in to_delete:
product = self.file.by_id(ifc_definition_id)
IfcStore.unlink_element(product)
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
@@ -104,9 +105,10 @@ class IfcExporter:
def sync_object_placement(self, obj):
blender_matrix = np.matrix(obj.matrix_world)
ifc_matrix = ifcopenshell.util.placement.get_local_placement(
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).ObjectPlacement
)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not hasattr(element, "ObjectPlacement"):
return
ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
ifc_matrix[0][3] *= self.unit_scale
ifc_matrix[1][3] *= self.unit_scale
ifc_matrix[2][3] *= self.unit_scale
@@ -124,7 +126,36 @@ class IfcExporter:
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
bpy.ops.bim.edit_object_placement(obj=obj.name)
def should_delete(self, guid, obj):
def sync_object_container(self, guid, obj):
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element_collection = bpy.data.collections.get(obj.name)
if self.file.schema == "IFC2X3":
if element.is_a("IfcProject"):
return
elif element.is_a("IfcContext"):
return
if (element.is_a("IfcElement") and element_collection) or element.is_a("IfcSpatialStructureElement"):
try:
parent_collection = [c for c in bpy.data.collections if c.children.get(element_collection.name)][0]
except:
return # Out of the spatial tree
else:
parent_collection = obj.users_collection[0]
parent_obj = bpy.data.objects.get(parent_collection.name)
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
return
parent = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"):
if parent != ifcopenshell.util.element.get_container(element):
bpy.ops.bim.assign_container(relating_structure=parent.id(), related_element=obj.name)
elif parent != ifcopenshell.util.element.get_aggregate(element):
bpy.ops.bim.assign_object(relating_object=parent_obj.name, related_object=obj.name)
def should_delete(self, obj):
try:
# This will throw an exception if the Blender object no longer exists
foo = obj.name
+35 -45
View File
@@ -6,20 +6,22 @@ import ifcopenshell.api.owner.settings
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.type.data import Data as TypeData
def mode_callback(obj, data):
if (
obj.mode != "OBJECT"
or not obj.data
or not isinstance(obj.data, bpy.types.Mesh)
or not obj.data.BIMMeshProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
IfcStore.edited_objs.add(obj.name)
for obj in bpy.context.selected_objects:
if (
obj.mode != "EDIT"
or not obj.data
or not isinstance(obj.data, bpy.types.Mesh)
or not obj.data.BIMMeshProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
IfcStore.edited_objs.add(obj.name)
def name_callback(obj, data):
@@ -29,6 +31,11 @@ def name_callback(obj, data):
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if not element.is_a("IfcRoot"):
return
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
collection = obj.users_collection[0]
collection.name = obj.name
if element.is_a("IfcTypeProduct"):
TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
@@ -52,52 +59,35 @@ def subscribe_to(object, data_path, callback):
def purge_module_data():
from blenderbim.bim import modules
for name in modules.keys():
for name, value in modules.items():
try:
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
except AttributeError:
pass
try:
getattr(value, "prop").purge()
except AttributeError:
pass
@persistent
def loadIfcStore(scene):
IfcStore.file = None
IfcStore.schema = None
props = bpy.context.scene.BIMProperties
IfcStore.id_map = (
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
)
IfcStore.guid_map = (
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
)
IfcStore.purge()
ifc_file = IfcStore.get_file()
IfcStore.get_schema()
[
IfcStore.link_element(ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id), o)
for o in bpy.data.objects
if o.BIMObjectProperties.ifc_definition_id
]
purge_module_data()
@persistent
def ensureIfcExported(scene):
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
# The invocation pops up a file select window.
# This is non-blocking, therefore the Blend file is saved before we export.
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
@persistent
def storeIdMap(scene):
try:
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
except:
# Regenerate maps. Is there a better solution for this? It seems fragile.
file = IfcStore.get_file()
IfcStore.id_map = {
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
}
IfcStore.guid_map = {
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
}
# Then attempt to store it again
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
bpy.ops.export_ifc.bim("INVOKE_DEFAULT")
def get_application(ifc):
@@ -172,12 +162,12 @@ def create_application_organisation(ifc):
@persistent
def setDefaultProperties(scene):
ifcopenshell.api.owner.settings.get_person = (
lambda ifc : ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
if bpy.context.scene.BIMOwnerProperties.user_person
else None
)
ifcopenshell.api.owner.settings.get_organisation = (
lambda ifc : ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
if bpy.context.scene.BIMOwnerProperties.user_organisation
else None
)
+15
View File
@@ -12,6 +12,21 @@ class IfcStore:
edited_objs = set()
pset_template_path = ""
pset_template_file = None
library_path = ""
library_file = None
@staticmethod
def purge():
IfcStore.path = ""
IfcStore.file = None
IfcStore.schema = None
IfcStore.id_map = {}
IfcStore.guid_map = {}
IfcStore.edited_objs = set()
IfcStore.pset_template_path = ""
IfcStore.pset_template_file = None
IfcStore.library_path = ""
IfcStore.library_file = None
@staticmethod
def get_file():
+3 -19
View File
@@ -780,9 +780,6 @@ class IfcImporter:
if element is None:
return
if not self.ifc_import_settings.should_import_spaces and element.is_a("IfcSpace"):
return
self.ifc_import_settings.logger.info("Creating object %s", element)
if mesh:
@@ -1003,6 +1000,7 @@ class IfcImporter:
bpy.ops.mesh.tris_convert_to_quads(context_override)
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
IfcStore.edited_objs.clear()
def add_opening_relation(self, element, obj):
if not element.is_a("IfcOpeningElement"):
@@ -1084,8 +1082,6 @@ class IfcImporter:
def add_related_objects(self, parent, related_objects):
for element in related_objects:
if element.is_a("IfcSpace"):
continue
global_id = element.GlobalId
collection = bpy.data.collections.new(self.get_name(element))
self.spatial_structure_elements[global_id] = {"blender": collection}
@@ -1129,8 +1125,6 @@ class IfcImporter:
container = element.ContainedInStructure[0].RelatingStructure
elif hasattr(element, "Decomposes") and element.Decomposes:
container = element.Decomposes[0].RelatingObject
if container.is_a("IfcSpace"):
return self.get_aggregate_container(container)
return container
def create_openings_collection(self):
@@ -1179,9 +1173,7 @@ class IfcImporter:
and element.ContainedInStructure[0].RelatingStructure
):
container = element.ContainedInStructure[0].RelatingStructure
if container.is_a("IfcSpace"):
return self.place_object_in_spatial_tree(container, obj)
elif element.is_a("IfcGrid"):
if element.is_a("IfcGrid"):
grid_collection = bpy.data.collections.get(obj.name)
if grid_collection: # Just in case we ran into invalid grids from Revit
self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
@@ -1193,22 +1185,15 @@ class IfcImporter:
if element.Decomposes[0].RelatingObject.is_a("IfcProject"):
collection = self.project["blender"]
elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"):
if element.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpace"):
if element.is_a("IfcSpatialStructureElement"):
global_id = element.GlobalId
else:
global_id = element.Decomposes[0].RelatingObject.GlobalId
if global_id in self.spatial_structure_elements:
if (
element.is_a("IfcSpatialStructureElement")
and not element.is_a("IfcSpace")
and "blender_obj" in self.spatial_structure_elements[global_id]
):
bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"])
collection = self.spatial_structure_elements[global_id]["blender"]
# This may occur if we are nesting an IfcSpace (which is special
# since it does not have a collection within an IfcSpace
if not collection:
return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj)
else:
collection = self.aggregates[element.Decomposes[0].RelatingObject.GlobalId]["blender"]
if collection:
@@ -1432,7 +1417,6 @@ class IfcImportSettings:
self.logger = None
self.input_file = None
self.diff_file = None
self.should_import_spaces = False
self.should_auto_set_workarounds = True
self.should_use_cpu_multiprocessing = True
self.should_merge_by_class = False
@@ -12,37 +12,41 @@ class AssignObject(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
props = related_object.BIMObjectProperties
relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
relating_object = bpy.data.objects.get(self.relating_object)
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
product = self.file.by_id(props.ifc_definition_id)
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
**{
"product": product,
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
},
)
bpy.ops.bim.edit_object_placement(obj=related_object.name)
Data.load(IfcStore.get_file(), props.ifc_definition_id)
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
product = self.file.by_id(oprops.ifc_definition_id)
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
**{
"product": product,
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
},
)
bpy.ops.bim.edit_object_placement(obj=related_object.name)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
self.remove_collection(bpy.context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
collection.children.link(spatial_collection)
continue
self.remove_collection(collection, spatial_collection)
else:
for collection in related_object.users_collection:
collection.objects.unlink(related_object)
relating_collection.objects.link(related_object)
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
self.remove_collection(bpy.context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
if not collection.children.get(spatial_collection.name):
collection.children.link(spatial_collection)
continue
self.remove_collection(collection, spatial_collection)
else:
for collection in related_object.users_collection:
collection.objects.unlink(related_object)
relating_collection.objects.link(related_object)
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -34,7 +34,8 @@ class BIM_PT_aggregate(Panel):
if props.is_editing_aggregate:
row = self.layout.row(align=True)
row.prop(props, "relating_object", text="")
row.operator("bim.assign_object", icon="CHECKMARK", text="")
if props.relating_object:
row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name
row.operator("bim.disable_editing_aggregate", icon="X", text="")
else:
row = self.layout.row(align=True)
@@ -17,6 +17,11 @@ from bpy.props import (
bcfviewpoints_enum = None
def purge():
global bcfviewpoints_enum
bcfviewpoints_enum = None
def updateBcfReferenceLink(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_reference_links()
@@ -18,6 +18,13 @@ scenarios_enum = []
classes_enum = []
def purge():
global scenarios_enum
global classes_enum
scenarios_enum = []
classes_enum = []
def getScenarios(self, context):
global scenarios_enum
if len(scenarios_enum) < 1:
@@ -16,6 +16,11 @@ from bpy.props import (
classification_enum = []
def purge():
global classification_enum
classification_enum = []
def getClassifications(self, context):
global classification_enum
if len(classification_enum) < 1:
@@ -104,18 +104,27 @@ class AddRepresentation(bpy.types.Operator):
return {"FINISHED"}
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
if (
box_context_id
and context_of_items.ContextType == "Model"
and context_of_items.ContextIdentifier
and context_of_items.ContextIdentifier == "Body"
):
if old_box:
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name)
representation_data["context"] = self.file.by_id(box_context_id)
new_box = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, **{"product": product, "representation": new_box}
)
[
bpy.ops.bim.add_style(material=s.material.name)
for s in obj.material_slots
if not s.material.BIMMaterialProperties.ifc_style_id
]
ifcopenshell.api.run(
"geometry.assign_styles",
self.file,
@@ -2,6 +2,7 @@ import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
@@ -44,6 +45,7 @@ class AddMaterial(bpy.types.Operator):
result = ifcopenshell.api.run("material.add_material", self.file, **{"Name": obj.name})
obj.BIMObjectProperties.ifc_definition_id = result.id()
Data.load(IfcStore.get_file())
material_prop_purge()
return {"FINISHED"}
@@ -316,6 +318,10 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
material_set_class = "IfcMaterialLayerSet"
elif product_data["type"] == "IfcMaterialProfileSet":
material_set_data = Data.profile_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialProfileSetUsage":
profile_set_usage = Data.profile_set_usages[product_data["id"]]
material_set_data = Data.profile_sets[profile_set_usage["ForProfileSet"]]
material_set_class = "IfcMaterialProfileSet"
elif product_data["type"] == "IfcMaterialList":
material_set_data = Data.lists[product_data["id"]]
else:
@@ -407,7 +413,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
material_set_item_data = Data.constituents[self.material_set_item]
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
material_set_item_data = Data.layers[self.material_set_item]
elif product_data["type"] == "IfcMaterialProfileSet":
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
material_set_item_data = Data.profiles[self.material_set_item]
else:
material_set_item_data = {}
@@ -540,7 +546,7 @@ class EditMaterialSetItem(bpy.types.Operator):
},
)
Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet":
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
profile_attributes = {}
for attribute in props.material_set_item_profile_attributes:
if attribute.data_type == "string":
@@ -21,6 +21,17 @@ profileclasses_enum = []
parameterizedprofileclasses_enum = []
def purge():
global materials_enum
global materialtypes_enum
global profileclasses_enum
global parameterizedprofileclasses_enum
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
def getProfileClasses(self, context):
global profileclasses_enum
if len(profileclasses_enum) == 0 and IfcStore.get_schema():
@@ -59,6 +70,7 @@ def getMaterialTypes(self, context):
"IfcMaterialLayerSet",
"IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet",
"IfcMaterialProfileSetUsage",
"IfcMaterialList",
]
if IfcStore.get_file().schema == "IFC2X3":
@@ -82,6 +82,13 @@ class BIM_PT_object_material(Panel):
self.set_items = self.material_set_data["MaterialProfiles"] or []
self.set_data = Data.profiles
self.set_item_name = "profile"
elif self.product_data["type"] == "IfcMaterialProfileSetUsage":
self.material_set_usage = Data.profile_set_usages[self.product_data["id"]]
self.material_set_id = self.material_set_usage["ForProfileSet"]
self.material_set_data = Data.profile_sets[self.material_set_id]
self.set_items = self.material_set_data["MaterialProfiles"] or []
self.set_data = Data.profiles
self.set_item_name = "profile"
elif self.product_data["type"] == "IfcMaterialList":
self.material_set_id = self.product_data["id"]
self.material_set_data = Data.lists[self.material_set_id]
@@ -18,6 +18,11 @@ from bpy.props import (
ifcpatchrecipes_enum = []
def purge():
global ifcpatchrecipes_enum
ifcpatchrecipes_enum = []
def getIfcPatchRecipes(self, context):
global ifcpatchrecipes_enum
if len(ifcpatchrecipes_enum) < 1:
@@ -5,8 +5,19 @@ classes = (
operator.CreateProject,
operator.CreateProjectLibrary,
operator.ValidateIfcFile,
operator.SelectLibraryFile,
operator.ChangeLibraryElement,
operator.RefreshLibrary,
operator.RewindLibrary,
operator.AssignLibraryDeclaration,
operator.UnassignLibraryDeclaration,
operator.SaveLibraryFile,
operator.AppendLibraryElement,
prop.LibraryElement,
prop.BIMProjectProperties,
ui.BIM_PT_project,
ui.BIM_PT_project_library,
ui.BIM_UL_library,
)
@@ -4,8 +4,7 @@ import ifcopenshell
import ifcopenshell.api
import bpy
from blenderbim.bim.ifc import IfcStore
# from ifcopenshell.api.project.data import Data
from blenderbim.bim import import_ifc
class CreateProject(bpy.types.Operator):
@@ -88,3 +87,171 @@ class ValidateIfcFile(bpy.types.Operator):
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
return {"FINISHED"}
class SelectLibraryFile(bpy.types.Operator):
bl_idname = "bim.select_library_file"
bl_label = "Select Library File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
IfcStore.library_path = self.filepath
IfcStore.library_file = ifcopenshell.open(self.filepath)
bpy.ops.bim.refresh_library()
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class RefreshLibrary(bpy.types.Operator):
bl_idname = "bim.refresh_library"
bl_label = "Refresh Library"
def execute(self, context):
self.props = context.scene.BIMProjectProperties
while len(self.props.library_elements) > 0:
self.props.library_elements.remove(0)
while len(self.props.library_breadcrumb) > 0:
self.props.library_breadcrumb.remove(0)
self.props.active_library_element = ""
types = IfcStore.library_file.wrapped_data.types_with_super()
if "IfcTypeProduct" in types:
new = self.props.library_elements.add()
new.name = "IfcTypeProduct"
return {"FINISHED"}
class ChangeLibraryElement(bpy.types.Operator):
bl_idname = "bim.change_library_element"
bl_label = "Change Library Element"
element_name: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMProjectProperties
ifc_classes = set()
self.props.active_library_element = self.element_name
crumb = self.props.library_breadcrumb.add()
crumb.name = self.element_name
elements = IfcStore.library_file.by_type(self.element_name)
[ifc_classes.add(e.is_a()) for e in elements]
while len(self.props.library_elements) > 0:
self.props.library_elements.remove(0)
if len(ifc_classes) == 1:
for element in elements:
new = self.props.library_elements.add()
new.name = element.Name or "Unnamed"
new.ifc_definition_id = element.id()
if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"):
new.is_declared = False
elif element.HasContext and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"):
new.is_declared = True
else:
for ifc_class in ifc_classes:
new = self.props.library_elements.add()
new.name = ifc_class
return {"FINISHED"}
class RewindLibrary(bpy.types.Operator):
bl_idname = "bim.rewind_library"
bl_label = "Rewind Library"
def execute(self, context):
self.props = context.scene.BIMProjectProperties
total_breadcrumbs = len(self.props.library_breadcrumb)
if total_breadcrumbs < 2:
bpy.ops.bim.refresh_library()
return {"FINISHED"}
element_name = self.props.library_breadcrumb[total_breadcrumbs - 2].name
self.props.library_breadcrumb.remove(total_breadcrumbs - 1)
self.props.library_breadcrumb.remove(total_breadcrumbs - 2)
bpy.ops.bim.change_library_element(element_name=element_name)
return {"FINISHED"}
class AssignLibraryDeclaration(bpy.types.Operator):
bl_idname = "bim.assign_library_declaration"
bl_label = "Assign Library Declaration"
definition: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMProjectProperties
ifcopenshell.api.run(
"project.assign_declaration",
IfcStore.library_file,
definition=IfcStore.library_file.by_id(self.definition),
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
)
element_name = self.props.active_library_element
bpy.ops.bim.rewind_library()
bpy.ops.bim.change_library_element(element_name = element_name)
return {"FINISHED"}
class UnassignLibraryDeclaration(bpy.types.Operator):
bl_idname = "bim.unassign_library_declaration"
bl_label = "Unassign Library Declaration"
definition: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMProjectProperties
ifcopenshell.api.run(
"project.unassign_declaration",
IfcStore.library_file,
definition=IfcStore.library_file.by_id(self.definition),
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
)
element_name = self.props.active_library_element
bpy.ops.bim.rewind_library()
bpy.ops.bim.change_library_element(element_name = element_name)
return {"FINISHED"}
class SaveLibraryFile(bpy.types.Operator):
bl_idname = "bim.save_library_file"
bl_label = "Save Library File"
def execute(self, context):
IfcStore.library_file.write(IfcStore.library_path)
return {"FINISHED"}
class AppendLibraryElement(bpy.types.Operator):
bl_idname = "bim.append_library_element"
bl_label = "Append Library Element"
definition: bpy.props.IntProperty()
def execute(self, context):
element = ifcopenshell.api.run(
"project.append_asset",
IfcStore.get_file(),
element=IfcStore.library_file.by_id(self.definition),
)
self.import_type_from_ifc(element)
return {"FINISHED"}
def import_type_from_ifc(self, element):
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
type_collection = bpy.data.collections.get("Types")
if not type_collection:
type_collection = bpy.data.collections.new("Types")
for collection in bpy.data.collections:
if "IfcProject/" in collection.name:
collection.children.link(type_collection)
break
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
ifc_importer.type_collection = type_collection
ifc_importer.create_type_product(element)
ifc_importer.place_objects_in_spatial_tree()
@@ -1,4 +1,5 @@
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -12,5 +13,15 @@ from bpy.props import (
)
class LibraryElement(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_declared: BoolProperty(name="Is Declared", default=False)
class BIMProjectProperties(PropertyGroup):
is_authoring: BoolProperty(name="Enable Authoring Mode", default=True)
active_library_element: StringProperty(name="Enable Authoring Mode", default="")
library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty)
library_elements: CollectionProperty(name="Library Elements", type=LibraryElement)
active_library_element_index: IntProperty(name="Active Library Element Index")
@@ -1,5 +1,5 @@
import os
from bpy.types import Panel
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
@@ -66,3 +66,68 @@ class BIM_PT_project(Panel):
if props.export_schema != "IFC2X3":
row = self.layout.row()
row.operator("bim.create_project_library")
class BIM_PT_project_library(Panel):
bl_label = "IFC Project Library"
bl_idname = "BIM_PT_project_library"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
self.props = context.scene.BIMProjectProperties
row = self.layout.row(align=True)
row.label(text=IfcStore.library_path or "No Library Loaded", icon="ASSET_MANAGER")
if IfcStore.library_file:
row.label(text=IfcStore.library_file.schema)
row.operator("bim.save_library_file", text="", icon="EXPORT")
row.operator("bim.select_library_file", icon="FILE_FOLDER", text="")
if IfcStore.library_file:
self.draw_library_ul()
def draw_library_ul(self):
if not self.props.library_elements:
row = self.layout.row()
row.label(text="No Assets Found", icon="ERROR")
return
row = self.layout.row(align=True)
row.label(text=self.props.active_library_element or "Top Level Assets")
if self.props.active_library_element:
row.operator("bim.rewind_library", icon="FRAME_PREV", text="")
row.operator("bim.refresh_library", icon="FILE_REFRESH", text="")
self.layout.template_list(
"BIM_UL_library",
"",
self.props,
"library_elements",
self.props,
"active_library_element_index",
)
class BIM_UL_library(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if not item.ifc_definition_id:
op = row.operator("bim.change_library_element", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.element_name = item.name
row.label(text=item.name)
if (
item.ifc_definition_id
and IfcStore.library_file.schema != "IFC2X3"
and IfcStore.library_file.by_type("IfcProjectLibrary")
):
if item.is_declared:
op = row.operator("bim.unassign_library_declaration", text="", icon="KEYFRAME_HLT", emboss=False)
op.definition = item.ifc_definition_id
else:
op = row.operator("bim.assign_library_declaration", text="", icon="KEYFRAME", emboss=False)
op.definition = item.ifc_definition_id
if item.ifc_definition_id:
op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND")
op.definition = item.ifc_definition_id
@@ -19,6 +19,13 @@ psetnames = {}
qtonames = {}
def purge():
global psetnames
global qtonames
psetnames = {}
qtonames = {}
def getPsetNames(self, context):
global psetnames
obj = context.active_object
@@ -21,6 +21,13 @@ psettemplatefiles_enum = []
psettemplates_enum = []
def purge():
global psettemplatefiles_enum
global psettemplates_enum
psettemplatefiles_enum = []
psettemplates_enum = []
def updatePsetTemplateFiles(self, context):
global psettemplates_enum
IfcStore.pset_template_path = os.path.join(
@@ -58,7 +65,7 @@ def getPsetTemplates(self, context):
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
templates = IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")
psettemplates_enum.extend([(str(t.id()), t.Name, "") for t in templates])
Data.load(IfcStore.get_file())
Data.load(IfcStore.pset_template_file)
return psettemplates_enum
@@ -35,10 +35,8 @@ class BIM_PT_pset_template(Panel):
row.operator("bim.enable_editing_pset_template", text="", icon="GREASEPENCIL")
row.operator("bim.remove_pset_template", text="", icon="X")
# row.operator("bim.save_pset_template", text="", icon="EXPORT")
if not Data.is_loaded and props.pset_template_files:
Data.load(IfcStore.get_file())
Data.load(IfcStore.pset_template_file)
if not Data.pset_templates:
return
@@ -76,6 +76,7 @@ class ReassignClass(bpy.types.Operator):
class AssignClass(bpy.types.Operator):
bl_idname = "bim.assign_class"
bl_label = "Assign IFC Class"
bl_options = {'REGISTER', 'UNDO'}
obj: bpy.props.StringProperty()
ifc_class: bpy.props.StringProperty()
predefined_type: bpy.props.StringProperty()
@@ -153,6 +154,7 @@ class AssignClass(bpy.types.Operator):
collection.objects.link(obj)
if parent_collection:
parent_collection.children.link(collection)
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=parent_collection.name)
else:
bpy.context.scene.collection.children.link(collection)
@@ -17,6 +17,15 @@ classes_enum = []
types_enum = []
def purge():
global products_enum
global classes_enum
global types_enum
products_enum = []
classes_enum = []
types_enum = []
def getIfcPredefinedTypes(self, context):
global types_enum
file = IfcStore.get_file()
@@ -39,9 +39,10 @@ class BIM_PT_class(Panel):
name += "[{}]".format(data["PredefinedType"])
row = self.layout.row(align=True)
row.label(text=name)
row.operator("bim.copy_class", icon="DUPLICATE", text="").obj = context.active_object.name
row.operator("bim.copy_class", icon="DUPLICATE", text="")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
if context.selected_objects:
row.operator("bim.unassign_class", icon="X", text="")
else:
@@ -2,55 +2,99 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadWorkPlans,
operator.DisableWorkPlanEditingUI,
operator.AddWorkPlan,
operator.EditWorkPlan,
operator.RemoveWorkPlan,
operator.EnableEditingWorkPlan,
operator.DisableEditingWorkPlan,
operator.LoadWorkSchedules,
operator.DisableWorkScheduleEditingUI,
operator.EnableEditingWorkPlanSchedules,
operator.AssignWorkSchedule,
operator.UnassignWorkSchedule,
operator.AddWorkSchedule,
operator.EditWorkSchedule,
operator.RemoveWorkSchedule,
operator.EnableEditingWorkSchedule,
operator.EnableEditingTasks,
operator.DisableEditingWorkSchedule,
operator.LoadTasks,
operator.DisableTaskEditingUI,
operator.LoadWorkCalendars,
operator.DisableWorkCalendarEditingUI,
operator.DisableEditingSequence,
operator.EditSequenceAttributes,
operator.EditSequenceTimeLag,
operator.EnableEditingSequenceAttributes,
operator.EnableEditingSequenceTimeLag,
operator.AddWorkCalendar,
operator.EditWorkCalendar,
operator.EditWorkTime,
operator.RemoveWorkCalendar,
operator.RemoveWorkTime,
operator.UnassignRecurrencePattern,
operator.UnassignLagTime,
operator.RemoveTimePeriod,
operator.EnableEditingWorkCalendar,
operator.EnableEditingWorkTime,
operator.EnableEditingWorkCalendarTimes,
operator.DisableEditingWorkCalendar,
operator.DisableEditingWorkTime,
operator.AddWorkTime,
operator.AssignLagTime,
operator.AssignRecurrencePattern,
operator.AddTimePeriod,
operator.AddTask,
operator.AddSummaryTask,
operator.ExpandTask,
operator.ContractTask,
operator.RemoveTask,
operator.EnableEditingTask,
operator.DisableEditingTask,
operator.DisableEditingTaskTime,
operator.EditTask,
operator.AssignPredecessor,
operator.AssignSuccessor,
operator.UnassignPredecessor,
operator.UnassignSuccessor,
operator.EnableEditingTaskTime,
operator.EnableEditingTaskCalendar,
operator.EnableEditingTaskSequence,
operator.EditTaskTime,
operator.EditTaskCalendar,
operator.RemoveTaskCalendar,
operator.AssignProduct,
operator.UnassignProduct,
operator.GenerateGanttChart,
operator.ImportP6,
operator.LoadTaskProperties,
operator.SelectTaskRelatedProducts,
operator.VisualiseWorkScheduleDate,
operator.VisualiseWorkScheduleDateRange,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.WorkSchedule,
prop.BIMWorkScheduleProperties,
prop.WorkCalendar,
prop.BIMWorkCalendarProperties,
prop.Task,
prop.BIMTaskProperties,
prop.BIMWorkScheduleProperties,
prop.BIMTaskTreeProperties,
prop.WorkCalendar,
prop.RecurrenceComponent,
prop.BIMWorkCalendarProperties,
ui.BIM_PT_work_plans,
ui.BIM_UL_work_plans,
ui.BIM_PT_work_schedules,
ui.BIM_UL_work_schedules,
ui.BIM_PT_work_calendars,
ui.BIM_UL_work_calendars,
ui.BIM_PT_tasks,
ui.BIM_UL_tasks,
)
def menu_func_import(self, context):
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
def register():
bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties)
bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties)
bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties)
bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties)
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
del bpy.types.Scene.BIMTaskProperties
del bpy.types.Scene.BIMWorkPlanProperties
del bpy.types.Scene.BIMWorkScheduleProperties
del bpy.types.Scene.BIMTaskTreeProperties
del bpy.types.Scene.BIMWorkCalendarProperties
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,9 @@
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
from blenderbim.bim.prop import StrProperty, Attribute
from dateutil import parser
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -13,16 +17,150 @@ from bpy.props import (
)
def updateTaskName(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled or self.name == "Unnamed":
return
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
**{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
)
Data.load(IfcStore.get_file())
if props.active_task_id == self.ifc_definition_id:
attribute = props.task_attributes.get("Name")
attribute.string_value = self.name
def updateTaskIdentification(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled or self.identification == "XXX":
return
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
**{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}},
)
Data.load(IfcStore.get_file())
if props.active_task_id == self.ifc_definition_id:
attribute = context.scene.BIMWorkScheduleProperties.task_attributes.get("Identification")
attribute.string_value = self.identification
def updateTaskTimeStart(self, context):
updateTaskTimeDateTime(self, context, "start")
def updateTaskTimeFinish(self, context):
updateTaskTimeDateTime(self, context, "finish")
def updateTaskTimeDateTime(self, context, startfinish):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled:
return
def canonicalise_time(time):
if not time:
return "-"
return time.strftime("%d/%m/%y")
startfinish_key = "Schedule" + startfinish.capitalize()
startfinish_value = getattr(self, startfinish)
if startfinish_value == "-":
return
self.file = IfcStore.get_file()
try:
startfinish_datetime = parser.isoparse(startfinish_value)
except:
try:
startfinish_datetime = parser.parse(startfinish_value, dayfirst=True, fuzzy=True)
except:
setattr(self, startfinish, "-")
return
task = self.file.by_id(self.ifc_definition_id)
if task.TaskTime:
task_time = task.TaskTime
else:
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task)
Data.load(IfcStore.get_file())
if Data.task_times[task_time.id()][startfinish_key] == startfinish_datetime:
canonical_startfinish_value = canonicalise_time(startfinish_datetime)
if startfinish_value != canonical_startfinish_value:
setattr(self, startfinish, canonical_startfinish_value)
return
ifcopenshell.api.run(
"sequence.edit_task_time",
self.file,
**{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}},
)
Data.load(IfcStore.get_file())
setattr(self, startfinish, canonicalise_time(startfinish_datetime))
def updateVisualisationStart(self, context):
updateVisualisationStartFinish(self, context, "visualisation_start")
def updateVisualisationFinish(self, context):
updateVisualisationStartFinish(self, context, "visualisation_finish")
def updateVisualisationStartFinish(self, context, startfinish):
def canonicalise_time(time):
if not time:
return "-"
return time.strftime("%d/%m/%y")
startfinish_value = getattr(self, startfinish)
try:
startfinish_datetime = parser.isoparse(startfinish_value)
except:
try:
startfinish_datetime = parser.parse(startfinish_value, dayfirst=True, fuzzy=True)
except:
setattr(self, startfinish, "-")
return
canonical_value = canonicalise_time(startfinish_datetime)
if startfinish_value != canonical_value:
setattr(self, startfinish, canonical_value)
workschedule_enum = []
def getWorkSchedules(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()]
def getWorkCalendars(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()]
class Task(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name", update=updateTaskName)
identification: StringProperty(name="Identification", update=updateTaskIdentification)
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMTaskProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
tasks: CollectionProperty(name="Tasks", type=Task)
active_task_index: IntProperty(name="Active Task Index")
has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
duration: StringProperty(name="Duration")
start: StringProperty(name="Start", update=updateTaskTimeStart)
finish: StringProperty(name="Finish", update=updateTaskTimeFinish)
derived_start: StringProperty(name="Derived Start")
derived_finish: StringProperty(name="Derived Finish")
derived_duration: StringProperty(name="Derived Duration")
is_predecessor: BoolProperty(name="Is Predecessor")
is_successor: BoolProperty(name="Is Successor")
class WorkPlan(PropertyGroup):
@@ -32,23 +170,54 @@ class WorkPlan(PropertyGroup):
class BIMWorkPlanProperties(PropertyGroup):
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
editing_type: StringProperty(name="Editing Type")
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
active_work_plan_index: IntProperty(name="Active Work Plan Index")
active_work_plan_id: IntProperty(name="Active Work Plan Id")
class WorkSchedule(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
work_schedules: EnumProperty(items=getWorkSchedules, name="Work Schedules")
class BIMWorkScheduleProperties(PropertyGroup):
work_calendars: EnumProperty(items=getWorkCalendars, name="Work Calendars")
work_schedule_attributes: CollectionProperty(name="Work Schedule Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
work_schedules: CollectionProperty(name="Work Schedules", type=WorkSchedule)
editing_type: StringProperty(name="Editing Type")
editing_task_type: StringProperty(name="Editing Task Type")
active_work_schedule_index: IntProperty(name="Active Work Schedules Index")
active_work_schedule_id: IntProperty(name="Active Work Schedules Id")
active_task_index: IntProperty(name="Active Task Index")
active_task_id: IntProperty(name="Active Task Id")
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
should_show_times: BoolProperty(name="Should Show Times", default=False)
active_task_time_id: IntProperty(name="Active Task Id")
task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute)
contracted_tasks: StringProperty(name="Contracted Task Items", default="[]")
is_task_update_enabled: BoolProperty(name="Is Task Update Enabled", default=True)
editing_sequence_type: StringProperty(name="Editing Sequence Type")
active_sequence_id: IntProperty(name="Active Sequence Id")
sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute)
time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute)
visualisation_start: StringProperty(name="Visualisation Start", update=updateVisualisationStart)
visualisation_finish: StringProperty(name="Visualisation Finish", update=updateVisualisationFinish)
speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000)
speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S")
speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24)
speed_real_duration: StringProperty(name="Speed Real Duration", default="P1W")
speed_types: EnumProperty(
items=[
("FRAME_SPEED", "Frame-based", "e.g. 25 frames = 1 real week"),
("DURATION_SPEED", "Duration-based", "e.g. 1 video second = 1 real week"),
("MULTIPLIER_SPEED", "Multiplier", "e.g. 1000 x real life speed"),
],
name="Speed Type",
default="FRAME_SPEED",
)
class BIMTaskTreeProperties(PropertyGroup):
# 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.
tasks: CollectionProperty(name="Tasks", type=Task)
class WorkCalendar(PropertyGroup):
@@ -56,9 +225,36 @@ class WorkCalendar(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
class RecurrenceComponent(PropertyGroup):
name: StringProperty(name="Name")
is_specified: BoolProperty(name="Is Specified")
class BIMWorkCalendarProperties(PropertyGroup):
work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar)
active_work_calendar_index: IntProperty(name="Active Work Calendar Index")
work_time_attributes: CollectionProperty(name="Work Time Attributes", type=Attribute)
editing_type: StringProperty(name="Editing Type")
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")
@@ -1,3 +1,4 @@
import isodate
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
@@ -19,28 +20,36 @@ class BIM_PT_work_plans(Panel):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
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.label(text="{} Work Plans Found".format(len(Data.work_plans)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_plan", text="", icon="ADD")
row.operator("bim.disable_work_plan_editing_ui", text="", icon="CHECKMARK")
row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT")
if self.props.active_work_plan_id == work_plan_id:
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:
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:
self.layout.template_list(
"BIM_UL_work_plans",
"",
self.props,
"work_plans",
self.props,
"active_work_plan_index",
)
if self.props.active_work_plan_id == work_plan_id:
if self.props.editing_type == "ATTRIBUTES":
self.draw_editable_ui()
elif self.props.editing_type == "SCHEDULES":
self.draw_work_schedule_ui()
if self.props.active_work_plan_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
def draw_editable_ui(self):
for attribute in self.props.work_plan_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
@@ -50,21 +59,20 @@ class BIM_PT_work_plans(Panel):
if attribute.is_optional:
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):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkPlanProperties.active_work_plan_id == item.ifc_definition_id:
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
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
for work_schedule_id in Data.work_plans[self.props.active_work_plan_id]["IsDecomposedBy"]:
work_schedule = Data.work_schedules[work_schedule_id]
row = self.layout.row(align=True)
row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
op = row.operator("bim.unassign_work_schedule", text="", icon="X")
op.work_plan = self.props.active_work_plan_id
op.work_schedule = int(self.props.work_schedules)
class BIM_PT_work_schedules(Panel):
@@ -80,31 +88,69 @@ class BIM_PT_work_schedules(Panel):
return IfcStore.get_file()
def draw(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.tprops = context.scene.BIMTaskTreeProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkScheduleProperties
row = self.layout.row()
row.operator("bim.add_work_schedule", icon="ADD")
for work_schedule_id, work_schedule in Data.work_schedules.items():
self.draw_work_schedule_ui(work_schedule_id, work_schedule)
def draw_work_schedule_ui(self, work_schedule_id, work_schedule):
row = self.layout.row(align=True)
row.label(text="{} Work Schedules Found".format(len(Data.work_schedules)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_schedule", text="", icon="ADD")
row.operator("bim.disable_work_schedule_editing_ui", text="", icon="CHECKMARK")
row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
if self.props.active_work_schedule_id and self.props.active_work_schedule_id == work_schedule_id:
if self.props.editing_type == "WORK_SCHEDULE":
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
elif self.props.editing_type == "TASKS":
row.prop(self.props, "should_show_times", text="", icon="TIME")
row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO")
row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id
row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id
row.operator("bim.disable_editing_work_schedule", text="", icon="CANCEL")
elif self.props.active_work_schedule_id:
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
else:
row.operator("bim.load_work_schedules", text="", icon="GREASEPENCIL")
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.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_work_schedules",
"",
self.props,
"work_schedules",
self.props,
"active_work_schedule_index",
)
if self.props.active_work_schedule_id == work_schedule_id:
if self.props.should_show_visualisation_ui:
self.draw_visualisation_ui()
if self.props.editing_type == "WORK_SCHEDULE":
self.draw_editable_work_schedule_ui()
elif self.props.editing_type == "TASKS":
self.draw_editable_task_ui(work_schedule_id)
if self.props.active_work_schedule_id:
self.draw_editable_ui(context)
def draw_visualisation_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "visualisation_start", text="", icon="REW")
row.prop(self.props, "visualisation_finish", text="", icon="FF")
op = row.operator("bim.visualise_work_schedule_date", text="", icon="RESTRICT_RENDER_OFF")
op.work_schedule = self.props.active_work_schedule_id
op = row.operator("bim.visualise_work_schedule_date_range", text="", icon="OUTLINER_OB_CAMERA")
op.work_schedule = self.props.active_work_schedule_id
def draw_editable_ui(self, context):
row = self.layout.row(align=True)
row.prop(self.props, "speed_types", text="")
if self.props.speed_types == "FRAME_SPEED":
row.prop(self.props, "speed_animation_frames", text="")
row.prop(self.props, "speed_real_duration", text="")
elif self.props.speed_types == "DURATION_SPEED":
row.prop(self.props, "speed_animation_duration", text="")
row.prop(self.props, "speed_real_duration", text="")
elif self.props.speed_types == "MULTIPLIER_SPEED":
row.prop(self.props, "speed_multiplier", text="")
def draw_editable_work_schedule_ui(self):
for attribute in self.props.work_schedule_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
@@ -114,21 +160,236 @@ class BIM_PT_work_schedules(Panel):
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_editable_task_ui(self, work_schedule_id):
self.layout.template_list(
"BIM_UL_tasks",
"",
self.tprops,
"tasks",
self.props,
"active_task_index",
)
if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES":
self.draw_editable_task_attributes_ui()
elif self.props.active_task_id and self.props.editing_task_type == "CALENDAR":
self.draw_editable_task_calendar_ui()
elif self.props.active_task_id and self.props.editing_task_type == "SEQUENCE":
self.draw_editable_task_sequence_ui()
elif self.props.active_task_time_id and self.props.editing_task_type == "TASKTIME":
self.draw_editable_task_time_attributes_ui()
class BIM_UL_work_schedules(UIList):
def draw_editable_task_sequence_ui(self):
task = Data.tasks[self.props.active_task_id]
row = self.layout.row()
row.label(text="{} Predecessors".format(len(task["IsSuccessorFrom"])), icon="BACK")
for sequence_id in task["IsSuccessorFrom"]:
self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatingProcess")
row = self.layout.row()
row.label(text="{} Successors".format(len(task["IsPredecessorTo"])), icon="FORWARD")
for sequence_id in task["IsPredecessorTo"]:
self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatedProcess")
def draw_editable_sequence_ui(self, sequence, process_type):
task = Data.tasks[sequence[process_type]]
row = self.layout.row(align=True)
row.label(text=task["Identification"] or "XXX")
row.label(text=task["Name"] or "Unnamed")
row.label(text=sequence["SequenceType"] or "N/A")
if sequence["TimeLag"]:
row.operator("bim.unassign_lag_time", text="", icon="X").sequence = sequence["id"]
row.label(text=isodate.duration_isoformat(Data.lag_times[sequence["TimeLag"]]["LagValue"]))
else:
row.operator("bim.assign_lag_time", text="", icon="ADD").sequence = sequence["id"]
row.label(text="N/A")
if self.props.active_sequence_id == sequence["id"]:
if self.props.editing_sequence_type == "ATTRIBUTES":
row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_sequence", text="", icon="X")
self.draw_editable_sequence_attributes_ui()
elif self.props.editing_sequence_type == "TIME_LAG":
op = row.operator("bim.edit_sequence_time_lag", text="", icon="CHECKMARK")
op.lag_time = sequence["TimeLag"]
row.operator("bim.disable_editing_sequence", text="", icon="X")
self.draw_editable_sequence_time_lag_ui()
else:
if sequence["TimeLag"]:
op = row.operator("bim.enable_editing_sequence_time_lag", text="", icon="CON_LOCKTRACK")
op.sequence = sequence["id"]
op.lag_time = sequence["TimeLag"]
op = row.operator("bim.enable_editing_sequence_attributes", text="", icon="GREASEPENCIL")
op.sequence = sequence["id"]
def draw_editable_sequence_attributes_ui(self):
for attribute in self.props.sequence_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="")
def draw_editable_sequence_time_lag_ui(self):
for attribute in self.props.time_lag_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="")
def draw_editable_task_calendar_ui(self):
task = Data.tasks[self.props.active_task_id]
if task["HasAssignmentsWorkCalendar"]:
row = self.layout.row(align=True)
calendar = Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]]
row.label(text=calendar["Name"] or "Unnamed")
op = row.operator("bim.remove_task_calendar", text="", icon="X")
op.work_calendar = task["HasAssignmentsWorkCalendar"][0]
op.task = self.props.active_task_id
else:
row = self.layout.row(align=True)
row.prop(self.props, "work_calendars", text="")
op = row.operator("bim.edit_task_calendar", text="", icon="ADD")
op.work_calendar = int(self.props.work_calendars)
op.task = self.props.active_task_id
def draw_editable_task_attributes_ui(self):
for attribute in self.props.task_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 == "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_task_time_attributes_ui(self):
for attribute in self.props.task_time_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_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMWorkScheduleProperties
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkScheduleProperties.active_work_schedule_id == item.ifc_definition_id:
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_schedule", text="", icon="X")
elif context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
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_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).task = item.ifc_definition_id
else:
row.operator(
"bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).task = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL")
op.work_schedule = item.ifc_definition_id
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
row.label(text="", icon="DOT")
row.prop(item, "identification", emboss=False, text="")
row.prop(item, "name", emboss=False, text="")
if props.should_show_times:
if item.derived_start:
row.label(text=item.derived_start + "*")
else:
row.prop(item, "start", emboss=False, text="")
if item.derived_finish:
row.label(text=item.derived_finish + "*")
else:
row.prop(item, "finish", emboss=False, text="")
if item.derived_duration:
row.label(text=item.derived_duration + "*")
else:
row.prop(item, "duration", emboss=False, text="")
if context.active_object:
oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True)
if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["RelatingProducts"]:
op = row.operator("bim.unassign_product", text="", icon="KEYFRAME_HLT", emboss=False)
op.task = item.ifc_definition_id
else:
op = row.operator("bim.assign_product", text="", icon="KEYFRAME", emboss=False)
op.task = item.ifc_definition_id
if props.active_task_id == item.ifc_definition_id:
if props.editing_task_type == "TASKTIME":
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
elif props.editing_task_type == "CALENDAR":
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
elif props.editing_task_type == "SEQUENCE":
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
elif props.editing_task_type == "ATTRIBUTES":
row.operator("bim.edit_task", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
elif props.active_task_id:
if props.editing_task_type == "SEQUENCE":
if item.is_predecessor:
row.operator(
"bim.unassign_predecessor", text="", icon="BACK", emboss=False
).task = item.ifc_definition_id
else:
row.operator(
"bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False
).task = item.ifc_definition_id
if item.is_successor:
row.operator(
"bim.unassign_successor", text="", icon="FORWARD", emboss=False
).task = item.ifc_definition_id
else:
row.operator(
"bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False
).task = item.ifc_definition_id
row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
else:
row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = item.ifc_definition_id
row.operator(
"bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text=""
).task = item.ifc_definition_id
row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id
row.operator(
"bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO"
).task = item.ifc_definition_id
row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id
row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
class BIM_PT_work_calendars(Panel):
@@ -147,29 +408,72 @@ class BIM_PT_work_calendars(Panel):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
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.label(text="{} Work Calendar Found".format(len(Data.work_calendars)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_calendar", text="", icon="ADD")
row.operator("bim.disable_work_calendar_editing_ui", text="", icon="CHECKMARK")
row.label(text=work_calendar["Name"] or "Unnamed", icon="VIEW_ORTHO")
if self.props.active_work_calendar_id == work_calendar_id:
if self.props.editing_type == "ATTRIBUTES":
row.operator("bim.edit_work_calendar", 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:
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:
self.layout.template_list(
"BIM_UL_work_calendars",
"",
self.props,
"work_calendars",
self.props,
"active_work_calendar_index",
)
if self.props.active_work_calendar_id == work_calendar_id:
if self.props.editing_type == "ATTRIBUTES":
self.draw_editable_ui()
elif self.props.editing_type == "WORKTIMES":
self.draw_work_times_ui(work_calendar_id, work_calendar)
if self.props.active_work_calendar_id:
self.draw_editable_ui(context)
def draw_work_times_ui(self, work_calendar_id, work_calendar):
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 attribute in self.props.work_calendar_attributes:
for work_time_id in work_calendar["WorkingTimes"]:
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="AUTO" if time_type == "WorkingTimes" else "HOME")
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)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
@@ -178,68 +482,78 @@ class BIM_PT_work_calendars(Panel):
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_work_calendars(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkCalendarProperties.active_work_calendar_id == item.ifc_definition_id:
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_calendar", text="", icon="X")
elif context.scene.BIMWorkCalendarProperties.active_work_calendar_id:
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL")
op.work_calendar = item.ifc_definition_id
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
class BIM_PT_tasks(Panel):
bl_label = "IFC Tasks"
bl_idname = "BIM_PT_tasks"
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):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMTaskProperties
row = self.layout.row(align=True)
row.label(text="{} Tasks Found".format(len(Data.tasks)), icon="ACTION")
if self.props.is_editing:
row.operator("bim.disable_task_editing_ui", text="", icon="CHECKMARK")
if work_time["RecurrencePattern"]:
self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]])
else:
row.operator("bim.load_tasks", text="", icon="GREASEPENCIL")
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
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_tasks",
"",
self.props,
"tasks",
self.props,
"active_task_index",
)
def draw_editable_recurrence_pattern_ui(self, recurrence_pattern):
box = self.layout.box()
row = box.row(align=True)
row.label(text=recurrence_pattern["RecurrenceType"], icon="RECOVER_LAST")
op = row.operator("bim.unassign_recurrence_pattern", text="", icon="X")
op.recurrence_pattern = recurrence_pattern["id"]
if self.props.active_task_index:
self.draw_editable_ui(context)
row = box.row(align=True)
row.prop(self.props, "start_time", text="")
row.prop(self.props, "end_time", text="")
op = row.operator("bim.add_time_period", text="", icon="ADD")
op.recurrence_pattern = recurrence_pattern["id"]
def draw_editable_ui(self, context):
pass
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"],
}
class BIM_UL_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.identification:
layout.label(text=item.identification)
layout.label(text=item.name)
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="")
@@ -13,44 +13,45 @@ class AssignContainer(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
related_element = (
bpy.data.objects.get(self.related_element) if self.related_element else bpy.context.active_object
related_elements = (
[bpy.data.objects.get(self.related_element)] if self.related_element else bpy.context.selected_objects
)
oprops = related_element.BIMObjectProperties
sprops = context.scene.BIMSpatialProperties
props = related_element.BIMObjectSpatialProperties
relating_structure = (
self.relating_structure or sprops.spatial_elements[sprops.active_spatial_element_index].ifc_definition_id
)
for related_element in related_elements:
oprops = related_element.BIMObjectProperties
props = related_element.BIMObjectSpatialProperties
ifcopenshell.api.run(
"spatial.assign_container",
self.file,
**{
"product": self.file.by_id(oprops.ifc_definition_id),
"relating_structure": self.file.by_id(relating_structure),
},
)
bpy.ops.bim.edit_object_placement(obj=related_element.name)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_container(obj=related_element.name)
ifcopenshell.api.run(
"spatial.assign_container",
self.file,
**{
"product": self.file.by_id(oprops.ifc_definition_id),
"relating_structure": self.file.by_id(relating_structure),
},
)
bpy.ops.bim.edit_object_placement(obj=related_element.name)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_container(obj=related_element.name)
aggregate_collection = bpy.data.collections.get(related_element.name)
aggregate_collection = bpy.data.collections.get(related_element.name)
relating_structure_obj = IfcStore.id_map.get(relating_structure)
relating_collection = None
if relating_structure_obj:
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
relating_structure_obj = IfcStore.id_map.get(relating_structure)
relating_collection = None
if relating_structure_obj:
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
if aggregate_collection:
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
for collection in bpy.data.collections:
self.remove_collection(collection, aggregate_collection)
relating_collection.children.link(aggregate_collection)
elif relating_collection:
for collection in related_element.users_collection:
collection.objects.unlink(related_element)
relating_collection.objects.link(related_element)
if aggregate_collection:
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
for collection in bpy.data.collections:
self.remove_collection(collection, aggregate_collection)
relating_collection.children.link(aggregate_collection)
elif relating_collection:
for collection in related_element.users_collection:
collection.objects.unlink(related_element)
relating_collection.objects.link(related_element)
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -20,13 +20,23 @@ classes = (
operator.EnableEditingStructuralConnectionCondition,
operator.DisableEditingStructuralConnectionCondition,
operator.RemoveStructuralConnectionCondition,
operator.EnableEditingStructuralMemberAxis,
operator.DisableEditingStructuralMemberAxis,
operator.EditStructuralMemberAxis,
operator.AddStructuralLoadCase,
operator.EditStructuralLoadCase,
operator.RemoveStructuralLoadCase,
operator.EnableEditingStructuralLoadCase,
operator.DisableEditingStructuralLoadCase,
prop.StructuralAnalysisModel,
prop.BIMStructuralProperties,
prop.BIMObjectStructuralProperties,
ui.BIM_PT_structural_analysis_models,
ui.BIM_PT_structural_boundary_conditions,
ui.BIM_PT_connected_structural_members,
ui.BIM_PT_structural_member,
ui.BIM_UL_structural_analysis_models,
ui.BIM_PT_structural_load_cases,
)
@@ -2,6 +2,8 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
from math import degrees
from mathutils import Vector, Matrix
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.structural.data import Data
from ifcopenshell.api.context.data import Data as ContextData
@@ -363,3 +365,210 @@ class UnassignStructuralAnalysisModel(bpy.types.Operator):
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EnableEditingStructuralMemberAxis(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_member_axis"
bl_label = "Enable Editing Structural Member Axis"
def execute(self, context):
obj = bpy.context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
self.file = IfcStore.get_file()
member = self.file.by_id(oprops.ifc_definition_id)
z_axis = Vector(member.Axis.DirectionRatios).normalized() @ obj.matrix_world if member.Axis else None
x_axis = (obj.data.vertices[1].co - obj.data.vertices[0].co).normalized()
location = obj.data.vertices[0].co
empty = bpy.data.objects.new("Member Axis", None)
empty.empty_display_type = "ARROWS"
if z_axis:
y_axis = (z_axis.cross(x_axis)).normalized()
empty.matrix_world = Matrix((
(x_axis[0], y_axis[0], z_axis[0], location[0]),
(x_axis[1], y_axis[1], z_axis[1], location[1]),
(x_axis[2], y_axis[2], z_axis[2], location[2]),
(0, 0, 0, 1),
))
else:
empty.location = location
empty.rotation_mode = "QUATERNION"
empty.rotation_quaternion = x_axis.to_track_quat("X", "Z")
props.axis_angle = degrees(empty.rotation_euler[0])
props.axis_empty = empty
context.scene.collection.objects.link(empty)
props.is_editing_axis = True
return {"FINISHED"}
class DisableEditingStructuralMemberAxis(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_member_axis"
bl_label = "Disable Editing Structural Member Axis"
def execute(self, context):
obj = bpy.context.active_object
props = obj.BIMStructuralProperties
props.is_editing_axis = False
if props.axis_empty:
bpy.data.objects.remove(props.axis_empty)
return {"FINISHED"}
class EditStructuralMemberAxis(bpy.types.Operator):
bl_idname = "bim.edit_structural_member_axis"
bl_label = "Edit Structural Member Axis"
def execute(self, context):
obj = bpy.context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
z_axis = relative_matrix.col[2][0:3]
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.edit_structural_member_axis",
self.file,
structural_member=self.file.by_id(oprops.ifc_definition_id),
axis=z_axis,
)
bpy.ops.bim.disable_editing_structural_member_axis()
return {"FINISHED"}
class AssignStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.assign_structural_load_case"
bl_label = "Assign Structural Load Case"
work_plan: bpy.props.IntProperty()
load_case: 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.load_case),
},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnassignStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.unassign_structural_load_case"
bl_label = "Unassign Structural Load Case"
work_plan: bpy.props.IntProperty()
load_case: 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.load_case),
},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class AddStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.add_structural_load_case"
bl_label = "Add Structural Load Case"
def execute(self, context):
ifcopenshell.api.run("structural.add_structural_load_case", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EditStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.edit_structural_load_case"
bl_label = "Edit Structural Load Case"
def execute(self, context):
props = context.scene.BIMStructuralProperties
attributes = {}
for attribute in props.load_case_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.edit_structural_load_case",
self.file,
**{"load_case": self.file.by_id(props.active_load_case_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_structural_load_case()
return {"FINISHED"}
class RemoveStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.remove_structural_load_case"
bl_label = "Remove Structural Load Case"
load_case: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.remove_structural_load_case", self.file, load_case=self.file.by_id(self.load_case)
)
Data.load(self.file)
return {"FINISHED"}
class EnableEditingStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_load_case"
bl_label = "Enable Editing Structural Load Case"
load_case: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.props.active_load_case_id = self.load_case
while len(self.props.load_case_attributes) > 0:
self.props.load_case_attributes.remove(0)
self.enable_editing_structural_load_case()
return {"FINISHED"}
def enable_editing_structural_load_case(self):
data = Data.load_cases[self.load_case]
print(data)
for attribute in IfcStore.get_schema().declaration_by_name("IfcStructuralLoadCase").all_attributes():
if attribute.name() in ["SelfWeightCoefficients", "Coefficient"]:
continue
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = self.props.load_case_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
print(data_type)
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
class DisableEditingStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_load_case"
bl_label = "Disable Editing Structural Load Case"
def execute(self, context):
context.scene.BIMStructuralProperties.active_load_case_id = 0
return {"FINISHED"}
@@ -1,4 +1,5 @@
import bpy
from math import radians
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -13,6 +14,19 @@ from bpy.props import (
)
def updateAxisAngle(self, context):
if not self.axis_empty:
return
obj = context.active_object
empty = self.axis_empty
x_axis = obj.data.vertices[1].co - obj.data.vertices[0].co
empty.location = obj.data.vertices[0].co
empty.rotation_mode = "QUATERNION"
empty.rotation_quaternion = x_axis.to_track_quat("X", "Z")
empty.rotation_mode = "XYZ"
empty.rotation_euler[0] = radians(self.axis_angle)
class StructuralAnalysisModel(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -27,9 +41,20 @@ class BIMStructuralProperties(PropertyGroup):
active_structural_analysis_model_index: IntProperty(name="Active Structural Analysis Model Index")
active_structural_analysis_model_id: IntProperty(name="Active Structural Analysis Model Id")
# editing_type: StringProperty(name="Editing Type")
# active_load_case_index: IntProperty(name="Active Work Schedules Index")
load_case_attributes: CollectionProperty(name="Load Case Attributes", type=Attribute)
active_load_case_id: IntProperty(name="Active Load Case Id")
class BIMObjectStructuralProperties(PropertyGroup):
boundary_condition_attributes: CollectionProperty(name="Boundary Condition Attributes", type=Attribute)
active_boundary_condition: IntProperty(name="Active Boundary Condition")
active_connects_structural_member: IntProperty(name="Active Connects Structural Member")
relating_structural_member: PointerProperty(name="Relating Structural Member", type=bpy.types.Object)
is_editing_axis: BoolProperty(name="Is Editing Axis", default=False)
axis_angle: FloatProperty(name="Axis Angle", update=updateAxisAngle)
axis_empty: PointerProperty(name="Axis Empty", type=bpy.types.Object)
# relating_structural_activity: PointerProperty(name="Relating Structural Activity", type=bpy.types.Object)
@@ -141,6 +141,44 @@ class BIM_PT_connected_structural_members(Panel):
draw_boundary_condition_ui(box, data["AppliedCondition"], data["id"], self.props)
class BIM_PT_structural_member(Panel):
bl_label = "IFC Structural Member"
bl_idname = "BIM_PT_structural_member"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralMember"):
return False
return True
def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMStructuralProperties
self.file = IfcStore.get_file()
if self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcStructuralCurveMember"):
if self.props.is_editing_axis:
row = self.layout.row(align=True)
row.prop(self.props, "axis_angle")
row.operator("bim.edit_structural_member_axis", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_structural_member_axis", text="", icon="CANCEL")
else:
row = self.layout.row()
row.operator("bim.enable_editing_structural_member_axis", text="Edit Axis", icon="GREASEPENCIL")
else:
row = self.layout.row()
row.label(text="TODO")
class BIM_PT_structural_analysis_models(Panel):
bl_label = "IFC Structural Analysis Models"
bl_idname = "BIM_PT_structural_analysis_models"
@@ -223,3 +261,56 @@ class BIM_UL_structural_analysis_models(UIList):
op.structural_analysis_model = item.ifc_definition_id
op = row.operator("bim.remove_structural_analysis_model", text="", icon="X")
op.structural_analysis_model = item.ifc_definition_id
class BIM_PT_structural_load_cases(Panel):
bl_label = "IFC Structural Load Cases"
bl_idname = "BIM_PT_structural_load_cases"
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.BIMStructuralProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
row = self.layout.row()
row.operator("bim.add_structural_load_case", icon="ADD")
for load_case_id, load_case in Data.load_cases.items():
self.draw_load_case_ui(load_case_id, load_case)
def draw_load_case_ui(self, load_case_id, load_case):
row = self.layout.row(align=True)
row.label(text=load_case["Name"] or "Unnamed", icon="CON_CLAMPTO")
if self.props.active_load_case_id and self.props.active_load_case_id == load_case_id:
row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_structural_load_case", text="", icon="CANCEL")
elif self.props.active_load_case_id:
row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id
else:
row.operator(
"bim.enable_editing_structural_load_case", text="", icon="GREASEPENCIL"
).load_case = load_case_id
row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id
if self.props.active_load_case_id == load_case_id:
self.draw_editable_load_case_ui()
def draw_editable_load_case_ui(self):
for attribute in self.props.load_case_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "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="")
@@ -20,6 +20,17 @@ type_classes_enum = []
available_types_enum = []
def purge():
global applicable_types_enum
global relating_types_enum
global type_classes_enum
global available_types_enum
applicable_types_enum = []
relating_types_enum = []
type_classes_enum = []
available_types_enum = []
def getIfcTypes(self, context):
global type_classes_enum
file = IfcStore.get_file()
+6 -5
View File
@@ -67,9 +67,11 @@ class ExportIFC(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
should_force_resave: bpy.props.BoolProperty(name="Resave .blend", default=False)
def invoke(self, context, event):
if bpy.context.scene.BIMProperties.ifc_file:
self.filepath = bpy.context.scene.BIMProperties.ifc_file
return self.execute(context)
if not self.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc")
WindowManager = context.window_manager
@@ -104,8 +106,8 @@ class ExportIFC(bpy.types.Operator):
new.name = output_file
if not bpy.context.scene.BIMProperties.ifc_file:
bpy.context.scene.BIMProperties.ifc_file = output_file
if self.should_force_resave:
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
return {"FINISHED"}
@@ -115,7 +117,6 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
should_import_spaces: bpy.props.BoolProperty(name="Import Spaces", default=False)
should_auto_set_workarounds: bpy.props.BoolProperty(name="Automatically Set Vendor Workarounds", default=True)
should_use_cpu_multiprocessing: bpy.props.BoolProperty(name="Import with CPU Multiprocessing", default=True)
should_merge_by_class: bpy.props.BoolProperty(name="Import and Merge by Class", default=False)
@@ -140,7 +141,6 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
)
settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger)
settings.should_import_spaces = self.should_import_spaces
settings.should_auto_set_workarounds = self.should_auto_set_workarounds
settings.should_use_cpu_multiprocessing = self.should_use_cpu_multiprocessing
settings.should_merge_by_class = self.should_merge_by_class
@@ -324,6 +324,7 @@ class SelectIfcFile(bpy.types.Operator):
bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
bpy.context.scene.BIMProperties.ifc_file = self.filepath
+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]]")
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_play_chaching_sound: BoolProperty(
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
)
def draw(self, context):
layout = self.layout
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.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.operator("bim.open_upstream", text="Visit Homepage").page = "home"
row.operator("bim.open_upstream", text="Visit Documentation").page = "docs"
-33
View File
@@ -1,33 +0,0 @@
import json
import ifcopenshell
class IfcToGantt:
def __init__(self):
self.json = []
def execute(self):
self.file = ifcopenshell.open("p6.ifc")
self.root = self.file.by_type("IfcTask")[0]
task = self.root
for task in self.file.by_type("IfcTask"):
self.json.append({
"pID": task.id(),
"pName": task.Name,
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
"pPlanEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
"pClass": "ggroupblack",
"pMile": 1 if task.IsMilestone else 0,
"pComp": 0,
"pGroup": 1,
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
"pOpen": 1,
"pCost": 1
})
with open("p6.json", "w") as f:
json.dump(self.json, f)
ifc_to_gantt = IfcToGantt()
ifc_to_gantt.execute()
-129
View File
@@ -1,129 +0,0 @@
import ifcopenshell
import ifcopenshell.util.date
import xml.etree.ElementTree as ET
class P6ToIfc:
def __init__(self):
self.project = {}
self.wbs = {}
def execute(self):
self.parse_xml()
self.create_ifc()
def parse_xml(self):
tree = ET.parse("p6.xml")
ns = {"pr": "http://xmlns.oracle.com/Primavera/P6/V19.12/API/BusinessObjects"}
root = tree.getroot()
project = root.find("pr:Project", ns)
self.project["Name"] = project.find("pr:Name", ns).text
for wbs in project.findall("pr:WBS", ns):
self.wbs[wbs.find("pr:ObjectId", ns).text] = {
"Name": wbs.find("pr:Name", ns).text,
"ParentObjectId": wbs.find("pr:ParentObjectId", ns).text,
"ifc": None,
"rel": None,
"activities": [],
}
for activity in project.findall("pr:Activity", ns):
self.wbs[activity.find("pr:WBSObjectId", ns).text]["activities"].append(
{
"Name": activity.find("pr:Name", ns).text,
"Identification": activity.find("pr:Id", ns).text,
"StartDate": activity.find("pr:StartDate", ns).text,
"FinishDate": activity.find("pr:FinishDate", ns).text,
"ifc": None,
}
)
def get_wbs(self, wbs):
return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []}
def create_ifc(self):
self.file = ifcopenshell.file(schema="IFC4")
# self.file = ifcopenshell.file(schema="IFC2X3")
self.root = self.file.create_entity(
"IfcTask", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.project["Name"]}
)
self.root_rel = self.create_rel_nests(self.root)
for wbs in self.wbs.values():
wbs["ifc"] = self.file.create_entity(
"IfcTask", **{"GlobalId": ifcopenshell.guid.new(), "Name": wbs["Name"]}
)
if wbs["ParentObjectId"]:
parent_wbs = self.wbs[wbs["ParentObjectId"]]
if not parent_wbs["rel"]:
parent_wbs["rel"] = self.create_rel_nests(parent_wbs["ifc"])
rel = parent_wbs["rel"]
else:
rel = self.root_rel
self.append_to_rel(rel, wbs["ifc"])
for activity in wbs["activities"]:
if not wbs["rel"]:
wbs["rel"] = self.create_rel_nests(wbs["ifc"])
if self.file.schema == "IFC2X3":
activity["TimeForTask"] = self.file.create_entity(
"IfcScheduleTimeControl",
**{
"ScheduleStart": self.file.create_entity(
"IfcCalendarDate",
**ifcopenshell.util.date.datetime2ifc(activity["StartDate"], "IfcCalendarDate")
),
"ScheduleFinish": self.file.create_entity(
"IfcCalendarDate",
**ifcopenshell.util.date.datetime2ifc(activity["FinishDate"], "IfcCalendarDate")
),
}
)
else:
activity["TaskTime"] = self.file.create_entity(
"IfcTaskTime",
**{"ScheduleStart": activity["StartDate"], "ScheduleFinish": activity["FinishDate"]}
)
is_milestone = activity["StartDate"] == activity["FinishDate"]
activity["ifc"] = self.file.create_entity(
"IfcTask",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": activity["Name"],
"Identification": activity["Identification"],
"IsMilestone": is_milestone,
}
)
if self.file.schema == "IFC2X3":
# Invalid, but reading the IFC2X3 docs gives me a headache
self.file.create_entity(
"IfcRelAssignsTasks",
**{
"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": [activity["ifc"]],
"TimeForTask": activity["TimeForTask"],
}
)
else:
activity["ifc"].TaskTime = activity["TaskTime"]
self.append_to_rel(wbs["rel"], activity["ifc"])
self.file.write("p6.ifc")
def create_rel_nests(self, relating_object):
return self.file.create_entity(
"IfcRelNests", **{"GlobalId": ifcopenshell.guid.new(), "RelatingObject": relating_object}
)
def append_to_rel(self, rel, element):
related_objects = list(rel.RelatedObjects or [])
related_objects.append(element)
rel.RelatedObjects = related_objects
p6_to_ifc = P6ToIfc()
p6_to_ifc.execute()
+5
View File
@@ -392,8 +392,11 @@ int main(int argc, char** argv) {
"Stores name and guid in a separate namespace as opposed to data-name, data-guid")
("svg-poly",
"Uses the polygonal algorithm for hidden line rendering")
("svg-write-poly",
"Approximate every curve as polygonal in SVG output")
("svg-project",
"Always enable hidden line rendering instead of only on elevations")
("svg-without-storeys", "Don't emit drawings for building storeys")
("door-arcs", "Draw door openings arcs for IfcDoor elements")
("section-height", po::value<double>(&section_height),
"Specifies the cut section height for SVG 2D geometry.")
@@ -1010,7 +1013,9 @@ int main(int argc, char** argv) {
}
static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0);
static_cast<SvgSerializer*>(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0);
static_cast<SvgSerializer*>(serializer.get())->setPolygonal(vmap.count("svg-write-poly") > 0);
static_cast<SvgSerializer*>(serializer.get())->setAlwaysProject(vmap.count("svg-project") > 0);
static_cast<SvgSerializer*>(serializer.get())->setWithoutStoreys(vmap.count("svg-without-storeys") > 0);
if (relative_center_x && relative_center_y) {
static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y);
}
+2 -1
View File
@@ -352,7 +352,8 @@ public:
IfcSchema::IfcSurfaceStyleShading* get_surface_style(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_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_convex(const TopoDS_Wire& wire);
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);
}
bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) {
TopTools_ListOfShape face_list;
TopExp_Explorer exp(compound, TopAbs_FACE);
bool IfcGeom::Kernel::shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li) {
TopExp_Explorer exp(s, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
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) {
return false;
}
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;
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.
// 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
// additional sewing is necessary.
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();
BRepOffsetAPI_Sewing sewer;
sewer.SetTolerance(getValue(GV_PRECISION));
sewer.SetMaxTolerance(getValue(GV_PRECISION));
sewer.SetMinTolerance(getValue(GV_PRECISION));
sewer.Add(result);
sewer.Add(BRepBuilderAPI_MakeFace(w1).Face());
sewer.Add(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
sewer.Perform();
result = sewer.SewedShape();
TopTools_ListOfShape li;
shape_to_face_list(result, li);
li.Append(BRepBuilderAPI_MakeFace(w1).Face().Reversed());
li.Append(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
create_solid_from_faces(li, result, true);
// @todo ugly hack
+44 -25
View File
@@ -30,6 +30,7 @@
#include <Bnd_Box.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepClass3d_SolidClassifier.hxx>
namespace IfcGeom {
@@ -38,6 +39,30 @@ namespace IfcGeom {
template <typename T>
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:
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> ts = select_box(t);
std::vector<T> select(const T& t, bool completely_within = false, double extend = 0.0) const {
std::vector<T> ts = select_box(t, completely_within, extend);
if (ts.empty()) {
return ts;
}
@@ -126,29 +151,18 @@ namespace IfcGeom {
continue;
}
if (completely_within) {
BRepAlgoAPI_Cut cut(B, A);
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);
}
}
if (test(A, B, completely_within, extend)) {
ts_filtered.push_back(*it);
}
}
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;
BRepBndLib::AddClose(s, bb);
bb.SetGap(bb.GetGap() + extend);
std::vector<T> ts;
@@ -156,7 +170,7 @@ namespace IfcGeom {
return ts;
}
ts = select_box(bb);
ts = select_box(bb, completely_within);
if (ts.empty()) {
return ts;
@@ -168,16 +182,13 @@ namespace IfcGeom {
typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second;
if (IfcGeom::Kernel::count(B, TopAbs_SHELL) == 0) {
continue;
}
BRepAlgoAPI_Common common(s, B);
if (common.IsDone()) {
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
ts_filtered.push_back(*it);
}
if (test(s, B, completely_within, extend)) {
ts_filtered.push_back(*it);
}
}
@@ -258,6 +269,10 @@ namespace IfcGeom {
add_file(f, settings);
}
tree(IfcGeom::Iterator<double>& it) {
add_file(it);
}
void add_file(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) {
IfcGeom::IteratorSettings settings_ = settings;
settings_.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
@@ -266,10 +281,14 @@ namespace IfcGeom {
IfcGeom::Iterator<double> it(settings_, &f);
add_file(it);
}
void add_file(IfcGeom::Iterator<double>& it) {
if (it.initialize()) {
do {
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());
}
}
@@ -12,6 +12,6 @@ class Usecase:
self.settings[key] = value
def execute(self):
assigned_items = set(self.settings["layer"].AssignedItems) or set()
assigned_items = set(self.settings["layer"].AssignedItems or [])
assigned_items.add(self.settings["item"])
self.settings["layer"].AssignedItems = list(assigned_items)
@@ -24,18 +24,28 @@ class Usecase:
elif self.settings["type"] == "IfcMaterialProfileSet":
material_set = self.file.create_entity(self.settings["type"])
self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
material_set = self.file.create_entity("IfcMaterialProfileSet")
material_set_usage = self.create_profile_set_usage(material_set)
self.create_material_association(material_set_usage)
elif self.settings["type"] == "IfcMaterialList":
material_set = self.file.create_entity(self.settings["type"])
material_set.Materials = [self.settings["material"]]
self.create_material_association(material_set)
def create_layer_set_usage(self, material_set):
return self.file.create_entity("IfcMaterialLayerSetUsage", **{
"ForLayerSet": material_set,
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
"DirectionSense": "POSITIVE",
"OffsetFromReferenceLine": 0
})
return self.file.create_entity(
"IfcMaterialLayerSetUsage",
**{
"ForLayerSet": material_set,
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
"DirectionSense": "POSITIVE",
"OffsetFromReferenceLine": 0,
}
)
def create_profile_set_usage(self, material_set):
return self.file.create_entity("IfcMaterialProfileSetUsage", **{"ForProfileSet": material_set})
def assign_ifc_material(self):
rel = self.get_rel_associates_material(self.settings["material"])
@@ -6,8 +6,10 @@ class Data:
materials = {}
constituent_sets = {}
constituents = {}
layer_sets_usages = {}
layer_sets = {}
layers = {}
profile_set_usages = {}
profile_sets = {}
profiles = {}
lists = {}
@@ -23,6 +25,7 @@ class Data:
cls.layer_set_usages = {}
cls.layer_sets = {}
cls.layers = {}
cls.profile_set_usages = {}
cls.profile_sets = {}
cls.profiles = {}
cls.lists = {}
@@ -41,6 +44,7 @@ class Data:
cls.load_layers()
cls.load_layer_usages()
cls.load_profiles()
cls.load_profile_usages()
cls.load_lists()
cls.is_loaded = True
@@ -68,6 +72,11 @@ class Data:
cls.layer_set_usages = {}
cls.load_element("IfcMaterialLayerSetUsage", cls.layer_set_usages)
@classmethod
def load_profile_usages(cls):
cls.profile_set_usages = {}
cls.load_element("IfcMaterialProfileSetUsage", cls.profile_set_usages)
@classmethod
def load_profiles(cls):
cls.profile_sets = {}
@@ -109,6 +118,4 @@ class Data:
@classmethod
def load_association(cls, association, product_id):
material_select = association.RelatingMaterial
if material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
material_select = material_select.ForProfileSet
cls.products[product_id] = {"type": material_select.is_a(), "id": material_select.id()}
@@ -1,5 +1,4 @@
import ifcopenshell
import blenderbim.bim.schema # TODO: refactor
class Usecase:
@@ -1,5 +1,4 @@
import ifcopenshell
import blenderbim.bim.schema # TODO: refactor
class Usecase:
@@ -1,6 +1,6 @@
import ifcopenshell
import ifcopenshell.util.attribute
import blenderbim.bim.schema # TODO: refactor elsewhere
import ifcopenshell.util.pset
class Data:
@@ -17,6 +17,7 @@ class Data:
@classmethod
def load(cls, file, product_id):
cls._file = file
cls._psetqto = ifcopenshell.util.pset.get_template("IFC4")
cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
if not file:
return
@@ -150,7 +151,7 @@ class Data:
@classmethod
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:
return
properties = []
@@ -1,4 +1,4 @@
import blenderbim.bim.schema # TODO: refactor
import ifcopenshell
class Usecase:
@@ -20,7 +20,9 @@ class Usecase:
self.settings["pset"].Name = self.settings["Name"]
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):
for prop in self.get_properties():
@@ -1,4 +1,4 @@
import blenderbim.bim.schema # TODO: refactor
import ifcopenshell
class Usecase:
@@ -20,7 +20,9 @@ class Usecase:
self.settings["qto"].Name = self.settings["Name"]
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):
for prop in self.settings["qto"].Quantities or []:
@@ -1,6 +1,3 @@
import blenderbim.bim.schema # TODO: refactor
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -5,7 +5,14 @@ class Data:
is_loaded = False
work_plans = {}
work_schedules = {}
work_calendars = {}
work_times = {}
recurrence_patterns = {}
time_periods = {}
tasks = {}
task_times = {}
lag_times = {}
sequences = {}
@classmethod
def purge(cls):
@@ -13,7 +20,13 @@ class Data:
cls.work_plans = {}
cls.work_schedules = {}
cls.work_calendars = {}
cls.work_times = {}
cls.recurrence_patterns = {}
cls.time_periods = {}
cls.tasks = {}
cls.task_times = {}
cls.lag_times = {}
cls.sequences = {}
@classmethod
def load(cls, file):
@@ -23,7 +36,13 @@ class Data:
cls.load_work_plans()
cls.load_work_schedules()
cls.load_work_calendars()
cls.load_work_times()
cls.load_recurrence_patterns()
cls.load_time_periods()
cls.load_tasks()
cls.load_task_times()
cls.load_lag_times()
cls.load_sequences()
cls.is_loaded = True
@classmethod
@@ -38,6 +57,9 @@ class Data:
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
if 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
@classmethod
@@ -52,19 +74,111 @@ class Data:
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
data["RelatedObjects"] = []
for rel in work_schedule.Controls:
for obj in rel.RelatedObjects:
if obj.is_a("IfcTask"):
data["RelatedObjects"].append(obj.id())
cls.work_schedules[work_schedule.id()] = data
@classmethod
def load_work_calendars(cls):
cls.work_calendars = {}
for work_calendar in cls._file.by_type("IfcWorkCalendar"):
data = work_calendar.get_info()
del data["OwnerHistory"]
del data["WorkingTimes"]
del data["ExceptionTimes"]
data["WorkingTimes"] = [t.id() for t in work_calendar.WorkingTimes or []]
data["ExceptionTimes"] = [t.id() for t in work_calendar.ExceptionTimes or []]
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
def load_tasks(cls):
cls.tasks = {}
for task in cls._file.by_type("IfcTask"):
cls.tasks[task.id()] = {"Name": task.Name, "Identification": task.Identification or ""}
data = task.get_info()
del data["OwnerHistory"]
data["HasAssignmentsWorkCalendar"] = []
data["RelatedObjects"] = []
data["RelatingProducts"] = []
data["IsPredecessorTo"] = []
data["IsSuccessorFrom"] = []
if task.TaskTime:
data["TaskTime"] = data["TaskTime"].id()
for rel in task.IsNestedBy:
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcTask")]
[
data["RelatingProducts"].append(r.RelatingProduct.id())
for r in task.HasAssignments
if r.is_a("IfcRelAssignsToProduct")
]
[data["IsPredecessorTo"].append(rel.id()) for rel in task.IsPredecessorTo or []]
[data["IsSuccessorFrom"].append(rel.id()) for rel in task.IsSuccessorFrom or []]
[
data["HasAssignmentsWorkCalendar"].append(rel.RelatingControl.id())
for rel in task.HasAssignments or []
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
]
cls.tasks[task.id()] = data
@classmethod
def load_task_times(cls):
cls.task_times = {}
for task_time in cls._file.by_type("IfcTaskTime"):
data = task_time.get_info()
for key, value in data.items():
if not value:
continue
if "Start" in key or "Finish" in key or key == "StatusTime":
data[key] = ifcopenshell.util.date.ifc2datetime(value)
elif key == "ScheduleDuration":
data[key] = ifcopenshell.util.date.ifc2datetime(value)
cls.task_times[task_time.id()] = data
@classmethod
def load_lag_times(cls):
cls.lag_times = {}
for lag_time in cls._file.by_type("IfcLagTime"):
data = lag_time.get_info()
if data["LagValue"]:
if data["LagValue"].is_a("IfcDuration"):
data["LagValue"] = ifcopenshell.util.date.ifc2datetime(data["LagValue"].wrappedValue)
else:
data["LagValue"] = float(data["LagValue"].wrappedValue)
cls.lag_times[lag_time.id()] = data
@classmethod
def load_sequences(cls):
cls.sequences = {}
for sequence in cls._file.by_type("IfcRelSequence"):
data = sequence.get_info()
data["RelatingProcess"] = sequence.RelatingProcess.id()
data["RelatedProcess"] = sequence.RelatedProcess.id()
data["TimeLag"] = sequence.TimeLag.id() if sequence.TimeLag else None
cls.sequences[sequence.id()] = data
@@ -1,3 +1,6 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -6,6 +6,12 @@ class Data:
boundary_conditions = {}
connects_structural_members = {}
load_cases = {}
# load_case_combinations = {}
# load_groups = {}
# structural_activities = {}
# connects_structural_activities = {}
@classmethod
def purge(cls):
cls.is_loaded = False
@@ -14,6 +20,7 @@ class Data:
cls.connections = {}
cls.boundary_conditions = {}
cls.connects_structural_members = {}
cls.load_cases = {}
@classmethod
def load(cls, file, product_id=None):
@@ -23,6 +30,7 @@ class Data:
if product_id:
return cls.load_structural_connection(product_id)
cls.load_structural_analysis_models()
cls.load_structural_load_cases()
cls.is_loaded = True
@classmethod
@@ -53,6 +61,27 @@ class Data:
cls.structural_analysis_models[model.id()] = data
@classmethod
def load_structural_load_cases(cls):
cls.load_cases = {}
for case in cls._file.by_type("IfcStructuralLoadCase"):
# if case.IsGroupedBy:
# for rel in case.IsGroupedBy:
# for product in rel.RelatedObjects:
# cls.products.setdefault(product.id(), []).append(case.id())
data = case.get_info()
del data["OwnerHistory"]
is_grouped_by = []
for load_group in case.IsGroupedBy or []:
is_grouped_by.append(load_group.id())
data["IsGroupedBy"] = is_grouped_by
cls.load_cases[case.id()] = data
print(data)
@classmethod
def load_structural_connection(cls, product_id):
cls.connections = {}
@@ -134,6 +134,9 @@ class tree(ifcopenshell_wrapper.tree):
def add_file(self, file, 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 unwrap(value):
+34 -23
View File
@@ -52,12 +52,18 @@ class facet(metaclass=meta_facet):
self.node = node
def __getattr__(self, k):
v = self.node.getElementsByTagName(k)[0]
elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE]
if elems:
return restriction(elems[0])
try:
v = self.node.getElementsByTagName(k)[0]
except IndexError:
v = None
if v:
elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE]
if elems:
return restriction(elems[0])
else:
return v.firstChild.nodeValue.strip()
else:
return v.firstChild.nodeValue.strip()
return None
def __iter__(self):
for k in self.parameters:
@@ -76,15 +82,18 @@ class entity(facet):
The IDS entity facet currently *with* inheritance
"""
parameters = ["name"]
message = "an entity name '%(name)s'"
parameters = ["name", "predefinedtype"]
def __call__(self, inst, logger):
logger.debug("Testing %s == %s", inst.is_a(), self.name)
# @nb with inheritance
# return inst.is_a() == self.name
return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()})
if self.predefinedtype and hasattr(inst, "PredefinedType"):
# logger.debug("Testing if entity predefinedtype '%s' == '%s'", inst.PredefinedType, self.predefinedtype)
self.message = "an entity name '%(name)s' of predefined type '%(predefinedtype)s'"
return facet_evaluation(inst.is_a(self.name) and inst.PredefinedType == self.predefinedtype, self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType})
else:
self.message = "an entity name '%(name)s'"
return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()})
class classification(facet):
"""
@@ -122,7 +131,7 @@ class property(facet):
props = ifcopenshell.util.element.get_psets(inst)
pset = props.get(self.propertyset)
val = pset.get(self.name) if pset else None
logger.debug("Testing %s == %s", val, self.value)
logger.debug("Testing if property %s == %s", val, self.value)
di = {
"name": self.name,
@@ -134,7 +143,7 @@ class property(facet):
msg = self.message % di
else:
if pset:
msg = "a set '%(propertyset)s', but no property '%(name)'" % di
msg = "a set '%(propertyset)s', but no property '%(name)s'" % di
else:
msg = "no set '%(propertyset)s'" % di
@@ -152,13 +161,13 @@ class material(facet):
material_relations = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")]
names = []
for rel in material_relations:
# @todo not all subtypes of IfcMaterial handled
if rel.RelatingMaterial.is_a() == "IfcMaterialLayerSetUsage":
layers = rel.RelatingMaterial.ForLayerSet.MaterialLayers
names = [layer.Material.Name for layer in layers]
elif rel.RelatingMaterial.is_a() == "IfcMaterial":
names.append(rel.RelatingMaterial.Name)
return facet_evaluation(
0,
# @todo
@@ -215,8 +224,7 @@ class restriction:
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"):
self.options.append(n.getAttribute("value"))
self.type = "pattern"
# "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
def __eq__(self, other):
return other in self.options
@@ -250,19 +258,19 @@ class specification:
phrases[0].tagName == "applicability" or error("expected <applicability>")
phrases[1].tagName == "requirements" or error("expected <requirements>")
self.applicabiliy, self.requirements = (boolean_and(parse_rules(phrase)) for phrase in phrases)
self.applicability, self.requirements = (boolean_and(parse_rules(phrase)) for phrase in phrases)
def __call__(self, inst, logger):
if self.applicabiliy(inst, logger):
if self.applicability(inst, logger):
valid = self.requirements(inst, logger)
if valid:
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant"})
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is compliant"})
else:
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant"})
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is not compliant"})
def __str__(self):
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__
class ids:
@@ -283,7 +291,7 @@ class ids:
for spec in self.specifications:
for elem in ifc_file.by_type("IfcObject"):
spec(elem, logger)
if __name__ == "__main__":
import sys, os
import logging
@@ -297,4 +305,7 @@ if __name__ == "__main__":
ids_file = ids(sys.argv[1])
ifc_file = ifcopenshell.open(sys.argv[2])
ids_file.validate(ifc_file, logger)
print("Validated %s IDS requirements on %s IFC elements. Results saved to %s" % (len(ids_file.specifications[0].requirements.terms), len(ifc_file.by_type('IfcProduct')), filename))
@@ -1,23 +1,42 @@
import datetime
from re import findall
from datetime import datetime
try:
import isodate
except:
pass # Duration parsing not supported
def duration2dict(duration):
results = {}
for number, unit in findall("(?P<number>\d+)(?P<period>S|M|H|D|W|Y)", duration):
results[unit] = number
return results
def timedelta2duration(timedelta):
components = {
"days": getattr(timedelta, "days", 0),
"hours": 0,
"minutes": 0,
"seconds": getattr(timedelta, "seconds", 0),
}
if components["seconds"]:
components["hours"], components["minutes"], components["seconds"] = [
int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":")
]
return isodate.Duration(**components)
def ifc2datetime(element):
if isinstance(element, str) and element[0] == "P": # IfcDuration
return duration2dict(element)
elif isinstance(element, str): # IfcDateTime, IfcDate
return datetime.fromisoformat(element)
duration = isodate.parse_duration(element)
if isinstance(duration, datetime.timedelta):
return timedelta2duration(duration)
return duration
elif isinstance(element, str) and element[2] == ":": # IfcTime
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
return datetime.fromtimestamp(element)
return datetime.datetime.fromtimestamp(element)
elif element.is_a("IfcDateAndTime"):
return datetime(
return datetime.datetime(
element.DateComponent.YearComponent,
element.DateComponent.MonthComponent,
element.DateComponent.DayComponent,
@@ -27,7 +46,7 @@ def ifc2datetime(element):
# TODO: implement TimeComponent timezone
)
elif element.is_a("IfcCalendarDate"):
return datetime(
return datetime.date(
element.YearComponent,
element.MonthComponent,
element.DayComponent,
@@ -36,15 +55,29 @@ def ifc2datetime(element):
def datetime2ifc(dt, ifc_type):
if isinstance(dt, str):
dt = datetime.fromisoformat(dt)
if ifc_type == "IfcTimeStamp":
if ifc_type == "IfcDuration":
return dt
dt = datetime.datetime.fromisoformat(dt)
if ifc_type == "IfcDuration":
return isodate.duration_isoformat(dt)
elif ifc_type == "IfcTimeStamp":
return int(dt.timestamp())
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":
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":
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":
return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year}
elif ifc_type == "IfcLocalTime":
@@ -88,6 +88,11 @@ def get_container(element):
return element.ContainedInStructure[0].RelatingStructure
def get_aggregate(element):
if hasattr(element, "Decomposes") and element.Decomposes:
return element.Decomposes[0].RelatingObject
def replace_attribute(element, old, new):
for i, attribute in enumerate(element):
if attribute == old:
@@ -119,17 +124,13 @@ def is_representation_of_context(representation, context, subcontext=None, targe
def remove_deep(ifc_file, element):
# @todo maybe some sort of try-finally mechanism.
ifc_file.batch()
subgraph = list(ifc_file.traverse(element))
subgraph_set = set(subgraph)
for ref in subgraph[::-1]:
if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0:
ifc_file.remove(ref)
def remove_deep_batched(ifc_file, element):
# @todo maybe some sort of try-finally mechanism.
ifc_file.batch()
remove_deep(ifc_file, element)
ifc_file.unbatch()
@@ -6,6 +6,15 @@ from typing import List, Generator, Optional
import ifcopenshell
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:
templates_path = {
+1
View File
@@ -45,6 +45,7 @@ namespace IfcUtil {
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);
}
+1 -1
View File
@@ -317,7 +317,7 @@ IfcCharacterEncoder::operator std::string() {
// Either 2 or 4 to uses \X2 or \X4 respectively.
// Currently hardcoded to 4, but \X2 might be
// sufficient for nearly all purposes.
const int num_bytes = *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);
bool in_extended = false;
+5 -9
View File
@@ -48,17 +48,13 @@ public:
: file(file_), id_(id), type_(type), attributes_(0), offset_in_file_(offset_in_file)
{}
IfcEntityInstanceData(IfcParse::IfcFile* file_, size_t size)
: file(file_), id_(0), type_(0), attributes_(new Argument*[size]), offset_in_file_(0)
IfcEntityInstanceData(IfcParse::IfcFile* file_, size_t size)
: file(file_), id_(0), type_(0), attributes_(new Argument*[size] {0}), offset_in_file_(0)
{}
IfcEntityInstanceData(const IfcParse::declaration* type)
: file(0), id_(0), type_(type), attributes_(new Argument*[getArgumentCount()])
{
for (size_t i = 0; i < getArgumentCount(); ++i) {
attributes_[i] = 0;
}
}
IfcEntityInstanceData(const IfcParse::declaration* type)
: file(0), id_(0), type_(type), attributes_(new Argument*[getArgumentCount()]{ 0 }), offset_in_file_(0)
{}
void load() const;
+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);
}
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>
void IfcHierarchyHelper<Schema>::setSurfaceColour(typename Schema::IfcRepresentation* rep,
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<Ifc4x2>;
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/Ifc4x2.h"
#include "../ifcparse/Ifc4x3_rc1.h"
#include "../ifcparse/Ifc4x3_rc2.h"
#include "../ifcparse/IfcFile.h"
+1 -1
View File
@@ -178,7 +178,7 @@ namespace IfcParse {
public:
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();
void read(IfcSpfLexer* t, std::vector<unsigned int>& ids);
-5
View File
@@ -43,11 +43,6 @@ HeaderEntity::HeaderEntity(const char * const datatype, size_t size, IfcFile* fi
if (file) {
offset_in_file_ = file->stream->Tell();
load();
} else {
// attributes_ = new Argument*[size];
for (size_t i = 0; i < size; ++i) {
attributes_[i] = 0;
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ def execute(args, is_library=None):
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
print("# Patching ...")
patcher.patch()
ifc_file = patcher.file
ifc_file = getattr(patcher, "file_patched", patcher.file)
if is_library is True:
return ifc_file
print("# Writing patched file ...")
+3 -3
View File
@@ -10,9 +10,9 @@ class Patcher:
self.args = args
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()
for element in self.file:
migrator.migrate(element, self.file_patched)
print("Migrating", element)
print("Successfully converted to", migrator.migrate(element, self.new))
self.file = self.new
print("Successfully converted to", migrator.migrate(element, self.file_patched))
+167 -86
View File
@@ -135,7 +135,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::option
// TODO: ALMOST_THE_SAME utilities in separate header
bool closed = fabs((u1 + PI2) - u2) < 1.e-9;
if (conical && closed) {
if (!polygonal_ && (conical && closed)) {
if (first) {
if (ty == STANDARD_TYPE(Geom_Circle)) {
Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve);
@@ -222,7 +222,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::option
growBoundingBox(p2.X(), p2.Y());
if (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse)) {
if (!polygonal_ && (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse))) {
Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(curve);
const bool mirrored = conic->Position().Axis().Direction().Z() < 0;
@@ -574,6 +574,11 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
b->second[0] - b->first[0],
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;
@@ -603,14 +608,37 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
}
}
if (!emit_building_storeys_ && scale && size) {
scale_ = scale;
size_ = std::make_pair(
// The header writes values in mm
size->first * 1000 * *scale_,
size->second * 1000 * *scale_
);
}
if (pln) {
// Move pln to have projection of origin at plane center.
// This is necessary to have Poly and BRep HLR at the same position
// (Poly) is wrong otherwise.
Extrema_ExtPElS ext;
ext.Perform(gp::Origin(), *pln, 1.e-5);
auto P0 = pln->Location();
pln->SetLocation(ext.Point(1).Value());
if (!emit_building_storeys_ && scale && size) {
auto P1 = pln->Location();
gp_Vec v(P1.XYZ() - P0.XYZ());
gp_Trsf pi;
pi.SetTransformation(pln->Position());
pi.Invert();
v.Transform(pi);
offset_2d_ = std::make_pair(
(-size->first / 2. - v.X()) * 1000 * *scale_,
(-size->second / 2. + v.Y()) * 1000 * *scale_
);
}
if (!deferred_section_data_) {
deferred_section_data_.emplace();
}
@@ -639,7 +667,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
element_buffer_.push_back(data);
}
write(data);
if (emit_building_storeys_) {
write(data);
}
}
namespace {
@@ -687,6 +717,17 @@ void SvgSerializer::write(const geometry_data& data) {
// (When determinant < 0, copy is implied and the input is not mutated.)
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_) {
BRepBndLib::Add(compound_unmirrored, bnd_);
}
@@ -1015,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());
}
auto z_local = gp::DZ().Transformed(data.trsf.Inverted());
if (data.product->declaration().is("IfcAnnotation") && // is an Annotation
(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,
// this excludes the upper bound with a small tolerance
is_floor_plan_
? (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;
@@ -1035,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") {
TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE);
TopExp_Explorer exp(subshape_to_use, TopAbs_EDGE, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const auto& e = TopoDS::Edge(exp.Current());
TopoDS_Vertex v0, v1;
@@ -1110,7 +1171,7 @@ void SvgSerializer::write(const geometry_data& data) {
} 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()) {
const auto& W = TopoDS::Wire(exp.Current());
write(*po, W, *dash_it);
@@ -1408,16 +1469,22 @@ std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
if (size_) {
// Scale the resulting image to a bounding rectangle specified by command line arguments
// or specified by IfcAnnotation[ObjectType=DRAWING]
const double dx = xmax - xmin;
const double dy = ymax - ymin;
double sc, cx, cy;
if (scale_) {
if (offset_2d_ && scale_) {
// offset_2d is the offset in plane u,v coordinates as we want to keep the
// plane coordinates used for HLR close to the model origin.
sc = (*scale_) * 1000;
cx = offset_2d_->first;
cy = offset_2d_->second;
} else if (scale_) {
sc = (*scale_) * 1000;
cx = (xmax + xmin) / 2. * sc - size_->first * center_x_.get_value_or(0.5);
cy = (ymax + ymin) / 2. * sc - size_->second * center_y_.get_value_or(0.5);
}
else {
} else {
if (calculated_scale_) {
sc = *calculated_scale_;
}
@@ -1618,87 +1685,92 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
gp_Trsf trsf;
if (kernel.convert_placement(*pl, trsf)) {
auto v = trsf.TranslationPart();
if (k.first) {
v.ChangeCoord(1) *= -1.;
trsf.SetTranslationPart(v);
}
auto v = gp_Pnt(trsf.TranslationPart());
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) {
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 ((!range || (v.Z() >= range->first && v.Z() < range->second)) && view_dir > 0.99) {
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
gp_Trsf trsf_view;
trsf_view.SetTransformation(gp::XOY(), meta.pln_3d.Position());
v.Transform(trsf_view);
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_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);
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()) {
// 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);
}
}
}
@@ -1708,6 +1780,8 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
}
void SvgSerializer::finalize() {
doWriteHeader();
for (auto& p : drawing_metadata) {
addTextAnnotations(p.first);
}
@@ -1815,6 +1889,8 @@ void SvgSerializer::finalize() {
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) {
auto storeys = this->file->instances_by_type("IfcBuildingStorey");
if (storeys) {
@@ -1900,6 +1976,11 @@ void SvgSerializer::finalize() {
}
void SvgSerializer::writeHeader() {
// This doesn't do anything anymore because there is now the option that an
// IfcAnnotation[ObjectType=DRAWING] defines the SVG viewBox and dimensions
}
void SvgSerializer::doWriteHeader() {
svg_file << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
if (use_namespace_) {
svg_file << " xmlns:ifc=\"http://www.ifcopenshell.org/ns\"";
+25 -3
View File
@@ -33,6 +33,11 @@
#include <HLRAlgo_Projector.hxx>
#include <gp_Pln.hxx>
#include <Bnd_Box.hxx>
#include <Standard_Version.hxx>
#if OCC_VERSION_HEX >= 0x70300
#include <Bnd_OBB.hxx>
#endif
#include <sstream>
#include <string>
@@ -136,16 +141,22 @@ protected:
double xmin, ymin, xmax, ymax;
boost::optional<std::vector<section_data>> section_data_;
boost::optional<std::vector<section_data>> deferred_section_data_;
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_, scale_backup_;
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_;
boost::optional<double> storey_height_line_length_;
boost::optional<std::pair<double, double>> size_, size_backup_;
boost::optional<std::pair<double, double>> size_, offset_2d_;
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_;
storey_height_display_types storey_height_display_;
bool draw_door_arcs_, is_floor_plan_;
bool auto_section_, auto_elevation_;
bool use_namespace_, use_hlr_poly_, always_project_;
bool use_namespace_, use_hlr_poly_, always_project_, polygonal_;
bool emit_building_storeys_;
IfcParse::IfcFile* file;
IfcUtil::IfcBaseEntity* storey_;
@@ -188,6 +199,8 @@ public:
, use_namespace_(false)
, use_hlr_poly_(false)
, always_project_(false)
, polygonal_(false)
, emit_building_storeys_(true)
, file(0)
, storey_(0)
, xcoords_begin(0)
@@ -200,6 +213,7 @@ public:
void addSizeComponent(const boost::shared_ptr<util::string_buffer::float_item>& fi) { radii.push_back(fi); }
void growBoundingBox(double x, double y) { if (x < xmin) xmin = x; if (x > xmax) xmax = x; if (y < ymin) ymin = y; if (y > ymax) ymax = y; }
void writeHeader();
void doWriteHeader();
bool ready();
void write(const IfcGeom::TriangulationElement<real_t>* /*o*/) {}
void write(const IfcGeom::BRepElement<real_t>* o);
@@ -249,10 +263,18 @@ public:
use_hlr_poly_ = b;
}
void setPolygonal(bool b) {
polygonal_ = b;
}
void setAlwaysProject(bool b) {
always_project_ = b;
}
void setWithoutStoreys(bool b) {
emit_building_storeys_ = !b;
}
void setScale(double s) { scale_ = s; }
void setDrawingCenter(double x, double y) {
center_x_ = x; center_y_ = y;