Merge pull request #1 from IfcOpenShell/v0.6.0

Upstream update
This commit is contained in:
ArturTomczak
2021-04-22 15:35:05 +02:00
committed by GitHub
88 changed files with 3053 additions and 560 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
+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:
+14
View File
@@ -177,6 +177,20 @@ 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 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
@@ -173,7 +173,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 +199,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)
@@ -0,0 +1,11 @@
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
<script src="jsgantt.js" type="text/javascript"></script>
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
<script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
var json_data = `
{{{json_data}}}
`;
JSGantt.parseJSONString(json_data, g);
g.Draw();
</script>
+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
+32 -45
View File
@@ -9,17 +9,18 @@ from ifcopenshell.api.attribute.data import Data as AttributeData
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 +30,9 @@ 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
element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
@@ -52,52 +56,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 +159,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():
+8 -20
View File
@@ -414,7 +414,11 @@ class IfcImporter:
self.exclude_elements |= self.native_elements
def is_native(self, element):
if not element.Representation or not element.Representation.Representations or element.HasOpenings:
if (
not element.Representation
or not element.Representation.Representations
or getattr(element, "HasOpenings", None)
):
return
representations = self.get_transformed_body_representations(element.Representation.Representations)
@@ -776,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:
@@ -999,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"):
@@ -1080,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}
@@ -1125,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):
@@ -1175,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)
@@ -1189,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:
@@ -1428,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:
@@ -1,16 +1,33 @@
import bpy
from . import ui, operator
from . import ui, prop, operator
classes = (
operator.AddCostSchedule,
operator.RemoveCostSchedule,
operator.EditCostSchedule,
operator.EditCostItem,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EnableEditingCostItem,
operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule,
operator.AddCostItem,
operator.AddSummaryCostItem,
operator.ExpandCostItem,
operator.ContractCostItem,
operator.RemoveCostItem,
operator.AssignControl,
operator.UnassignControl,
prop.CostItem,
prop.BIMCostProperties,
ui.BIM_PT_cost_schedules,
ui.BIM_UL_cost_items,
)
def register():
pass
bpy.types.Scene.BIMCostProperties = bpy.props.PointerProperty(type=prop.BIMCostProperties)
def unregister():
pass
del bpy.types.Scene.BIMCostProperties
@@ -1,4 +1,5 @@
import bpy
import json
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.cost.data import Data
@@ -14,6 +15,32 @@ class AddCostSchedule(bpy.types.Operator):
return {"FINISHED"}
class EditCostSchedule(bpy.types.Operator):
bl_idname = "bim.edit_cost_schedule"
bl_label = "Edit Cost Schedule"
def execute(self, context):
props = context.scene.BIMCostProperties
attributes = {}
for attribute in props.cost_schedule_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_schedule",
self.file,
**{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_cost_schedule()
return {"FINISHED"}
class RemoveCostSchedule(bpy.types.Operator):
bl_idname = "bim.remove_cost_schedule"
bl_label = "Remove Cost Schedule"
@@ -27,3 +54,276 @@ class RemoveCostSchedule(bpy.types.Operator):
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EnableEditingCostSchedule(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_schedule"
bl_label = "Enable Editing Cost Schedule"
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.props.active_cost_schedule_id = self.cost_schedule
while len(self.props.cost_schedule_attributes) > 0:
self.props.cost_schedule_attributes.remove(0)
self.enable_editing_cost_schedule()
self.props.is_editing = "COST_SCHEDULE"
return {"FINISHED"}
def enable_editing_cost_schedule(self):
data = Data.cost_schedules[self.cost_schedule]
for attribute in IfcStore.get_schema().declaration_by_name("IfcCostSchedule").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = self.props.cost_schedule_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if attribute.name() in ["SubmittedOn", "UpdateDate"]:
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
class EnableEditingCostItems(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_items"
bl_label = "Enable Editing Cost Items"
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.props.active_cost_schedule_id = self.cost_schedule
while len(self.props.cost_items) > 0:
self.props.cost_items.remove(0)
self.contracted_cost_items = json.loads(self.props.contracted_cost_items)
for related_object_id in Data.cost_schedules[self.cost_schedule]["RelatedObjects"]:
self.create_new_cost_item_li(related_object_id, 0)
self.props.is_editing = "COST_ITEMS"
return {"FINISHED"}
def create_new_cost_item_li(self, related_object_id, level_index):
cost_item = Data.cost_items[related_object_id]
new = self.props.cost_items.add()
new.ifc_definition_id = related_object_id
new.name = cost_item["Name"] or "Unnamed"
new.is_expanded = related_object_id not in self.contracted_cost_items
new.level_index = level_index
if cost_item["RelatedObjects"]:
new.has_children = True
if new.is_expanded:
for related_object_id in cost_item["RelatedObjects"]:
self.create_new_cost_item_li(related_object_id, level_index + 1)
return {"FINISHED"}
class DisableEditingCostSchedule(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_schedule"
bl_label = "Disable Editing Cost Schedule"
def execute(self, context):
context.scene.BIMCostProperties.active_cost_schedule_id = 0
return {"FINISHED"}
class AddSummaryCostItem(bpy.types.Operator):
bl_idname = "bim.add_summary_cost_item"
bl_label = "Add Cost Item"
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_schedule": self.file.by_id(self.cost_schedule)})
Data.load(self.file)
bpy.ops.bim.enable_editing_cost_items(cost_schedule=self.cost_schedule)
return {"FINISHED"}
class AddCostItem(bpy.types.Operator):
bl_idname = "bim.add_cost_item"
bl_label = "Add Cost Item"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_item": self.file.by_id(self.cost_item)})
Data.load(self.file)
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
return {"FINISHED"}
class ExpandCostItem(bpy.types.Operator):
bl_idname = "bim.expand_cost_item"
bl_label = "Expand Cost Item"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
contracted_cost_items = json.loads(props.contracted_cost_items)
contracted_cost_items.remove(self.cost_item)
props.contracted_cost_items = json.dumps(contracted_cost_items)
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
return {"FINISHED"}
class ContractCostItem(bpy.types.Operator):
bl_idname = "bim.contract_cost_item"
bl_label = "Contract Cost Item"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
contracted_cost_items = json.loads(props.contracted_cost_items)
contracted_cost_items.append(self.cost_item)
props.contracted_cost_items = json.dumps(contracted_cost_items)
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
return {"FINISHED"}
class RemoveCostItem(bpy.types.Operator):
bl_idname = "bim.remove_cost_item"
bl_label = "Remove Cost item"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.remove_cost_item",
self.file,
cost_item=self.file.by_id(self.cost_item),
)
contracted_cost_items = json.loads(props.contracted_cost_items)
if props.active_cost_item_index in contracted_cost_items:
contracted_cost_items.remove(props.active_cost_item_index)
props.contracted_cost_items = json.dumps(contracted_cost_items)
Data.load(self.file)
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
return {"FINISHED"}
class EnableEditingCostItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item"
bl_label = "Enable Editing Cost Item"
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
while len(props.cost_item_attributes) > 0:
props.cost_item_attributes.remove(0)
data = Data.cost_items[self.cost_item]
for attribute in IfcStore.get_schema().declaration_by_name("IfcCostItem").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or isinstance(data_type, tuple):
continue
new = props.cost_item_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_cost_item_id = self.cost_item
return {"FINISHED"}
class DisableEditingCostItem(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item"
bl_label = "Disable Editing Cost Item"
def execute(self, context):
context.scene.BIMCostProperties.active_cost_item_id = 0
return {"FINISHED"}
class EditCostItem(bpy.types.Operator):
bl_idname = "bim.edit_cost_item"
bl_label = "Edit Cost Item"
def execute(self, context):
props = context.scene.BIMCostProperties
attributes = {}
for attribute in props.cost_item_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "boolean":
attributes[attribute.name] = attribute.bool_value
elif attribute.data_type == "integer":
attributes[attribute.name] = attribute.int_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
**{"cost_item": self.file.by_id(props.active_cost_item_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_cost_item()
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
return {"FINISHED"}
class AssignControl(bpy.types.Operator):
bl_idname = "bim.assign_control"
bl_label = "Assign Control"
cost_item: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_control=self.file.by_id(self.cost_item),
)
Data.load(self.file)
return {"FINISHED"}
class UnassignControl(bpy.types.Operator):
bl_idname = "bim.unassign_control"
bl_label = "Unassign Control"
cost_item: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"control.unassign_control",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_control=self.file.by_id(self.cost_item),
)
Data.load(self.file)
return {"FINISHED"}
@@ -0,0 +1,51 @@
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.cost.data import Data
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def updateCostItemName(self, context):
if self.name == "Unnamed":
return
self.file = IfcStore.get_file()
props = context.scene.BIMCostProperties
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
)
Data.load(IfcStore.get_file())
if props.active_cost_item_id == self.ifc_definition_id:
attribute = props.cost_item_attributes.get("Name")
attribute.string_value = self.name
class CostItem(PropertyGroup):
name: StringProperty(name="Name", update=updateCostItemName)
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
class BIMCostProperties(PropertyGroup):
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing")
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
cost_items: CollectionProperty(name="Work Calendar", type=CostItem)
active_cost_item_id: IntProperty(name="Active Cost Id")
active_cost_item_index: IntProperty(name="Active Cost Item Index")
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
+107 -3
View File
@@ -16,6 +16,8 @@ class BIM_PT_cost_schedules(Panel):
return IfcStore.get_file()
def draw(self, context):
self.props = context.scene.BIMCostProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
@@ -23,7 +25,109 @@ class BIM_PT_cost_schedules(Panel):
row.operator("bim.add_cost_schedule", icon="ADD")
for cost_schedule_id, cost_schedule in Data.cost_schedules.items():
row = self.layout.row(align=True)
row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
row.operator("bim.add_cost_schedule", text="", icon="GREASEPENCIL")
self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule)
def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule):
row = self.layout.row(align=True)
row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id:
if self.props.is_editing == "COST_SCHEDULE":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "COST_ITEMS":
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule_id
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
elif self.props.active_cost_schedule_id:
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
else:
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule_id
row.operator(
"bim.enable_editing_cost_schedule", text="", icon="GREASEPENCIL"
).cost_schedule = cost_schedule_id
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
if self.props.active_cost_schedule_id == cost_schedule_id:
if self.props.is_editing == "COST_SCHEDULE":
self.draw_editable_cost_schedule_ui()
elif self.props.is_editing == "COST_ITEMS":
self.draw_editable_cost_item_ui(cost_schedule_id)
def draw_editable_cost_schedule_ui(self):
for attribute in self.props.cost_schedule_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="")
def draw_editable_cost_item_ui(self, cost_schedule_id):
self.layout.template_list(
"BIM_UL_cost_items",
"",
self.props,
"cost_items",
self.props,
"active_cost_item_index",
)
if self.props.active_cost_item_id:
self.draw_editable_cost_item_attributes_ui()
def draw_editable_cost_item_attributes_ui(self):
for attribute in self.props.cost_item_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="")
class BIM_UL_cost_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMCostProperties
row = layout.row(align=True)
for i in range(0, item.level_index):
row.label(text="", icon="BLANK1")
if item.has_children:
if item.is_expanded:
row.operator(
"bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).cost_item = item.ifc_definition_id
else:
row.operator(
"bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).cost_item = item.ifc_definition_id
else:
row.label(text="", icon="DOT")
row.prop(item, "name", emboss=False, text="")
if context.active_object:
oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True)
if oprops.ifc_definition_id in Data.cost_items[item.ifc_definition_id]["Controls"]:
op = row.operator("bim.unassign_control", text="", icon="KEYFRAME_HLT", emboss=False)
op.cost_item = item.ifc_definition_id
else:
op = row.operator("bim.assign_control", text="", icon="KEYFRAME", emboss=False)
op.cost_item = item.ifc_definition_id
if props.active_cost_item_id == item.ifc_definition_id:
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
elif props.active_cost_item_id:
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
else:
row.operator(
"bim.enable_editing_cost_item", text="", icon="GREASEPENCIL"
).cost_item = item.ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
@@ -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,
@@ -47,7 +47,7 @@ class EnableEditingLayer(bpy.types.Operator):
for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
if data_type == "entity" or data_type == "select":
continue
new = props.layer_attributes.add()
new.name = attribute.name()
@@ -10,6 +10,7 @@ classes = (
operator.RemoveConstituent,
operator.AddProfile,
operator.RemoveProfile,
operator.AssignParameterizedProfile,
operator.AddLayer,
operator.RemoveLayer,
operator.ReorderMaterialSetItem,
@@ -1,8 +1,37 @@
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
class AssignParameterizedProfile(bpy.types.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
ifc_class: bpy.props.StringProperty()
material_profile: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
self.file,
**{"ifc_class": self.ifc_class},
)
ifcopenshell.api.run(
"material.assign_profile",
self.file,
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile}
)
Data.load_profiles()
ProfileData.load(self.file)
bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile)
return {"FINISHED"}
class AddMaterial(bpy.types.Operator):
@@ -16,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"}
@@ -288,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:
@@ -370,8 +404,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = self.material_set_item
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set_item = self.file.by_id(self.material_set_item)
@@ -379,22 +413,29 @@ 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 = {}
props.material_set_item_material = str(material_set_item_data["Material"])
self.props.material_set_item_material = str(material_set_item_data["Material"])
while len(props.material_set_item_attributes) > 0:
props.material_set_item_attributes.remove(0)
self.load_set_item_attributes(material_set_item, material_set_item_data)
if material_set_item.is_a("IfcMaterialProfile"):
self.load_profile_attributes(material_set_item, material_set_item_data)
return {"FINISHED"}
def load_set_item_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_attributes) > 0:
self.props.material_set_item_attributes.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in material_set_item_data:
new = props.material_set_item_attributes.add()
new = self.props.material_set_item_attributes.add()
new.name = attribute.name()
new.is_null = material_set_item_data[attribute.name()] is None
new.data_type = data_type
@@ -406,7 +447,45 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
return {"FINISHED"}
def load_profile_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_profile_attributes) > 0:
self.props.material_set_item_profile_attributes.remove(0)
if not material_set_item_data["Profile"]:
return
profile = self.file.by_id(material_set_item_data["Profile"])
profile_data = ProfileData.profiles[material_set_item_data["Profile"]]
for attribute in IfcStore.get_schema().declaration_by_name(profile.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in profile_data:
new = self.props.material_set_item_profile_attributes.add()
new.name = attribute.name()
new.is_null = profile_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else profile_data[attribute.name()]
elif data_type == "float":
new.float_value = 0.0 if new.is_null else profile_data[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else profile_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else profile_data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if profile_data[attribute.name()]:
new.enum_value = profile_data[attribute.name()]
# Force null to be false if the attribute is mandatory because when we first assign a profile, all of
# its fields are null (which is illegal).
# TODO: find a better solution.
if not new.is_optional:
new.is_null = False
class DisableEditingMaterialSetItem(bpy.types.Operator):
@@ -467,17 +546,32 @@ 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":
value = attribute.string_value
elif attribute.data_type == "float":
value = attribute.float_value
elif attribute.data_type == "integer":
value = attribute.int_value
elif attribute.data_type == "boolean":
value = attribute.bool_value
elif attribute.data_type == "enum":
value = attribute.enum_value
profile_attributes[attribute.name] = None if attribute.is_null else value
ifcopenshell.api.run(
"material.edit_profile",
self.file,
**{
"profile": self.file.by_id(self.material_set_item),
"attributes": attributes,
"profile_attributes": profile_attributes,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
},
)
Data.load_profiles()
ProfileData.load(self.file)
else:
pass
@@ -1,5 +1,5 @@
import bpy
import blenderbim.bim.schema # refactor
import blenderbim.bim.schema # refactor
from ifcopenshell.api.material.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
@@ -17,6 +17,40 @@ from bpy.props import (
materials_enum = []
materialtypes_enum = []
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():
profileclasses_enum.clear()
profileclasses_enum = [
(t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
]
return profileclasses_enum
def getParameterizedProfileClasses(self, context):
global parameterizedprofileclasses_enum
if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema():
parameterizedprofileclasses_enum.clear()
parameterizedprofileclasses_enum = [
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
return parameterizedprofileclasses_enum
def getMaterials(self, context):
@@ -36,6 +70,7 @@ def getMaterialTypes(self, context):
"IfcMaterialLayerSet",
"IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet",
"IfcMaterialProfileSetUsage",
"IfcMaterialList",
]
if IfcStore.get_file().schema == "IFC2X3":
@@ -52,4 +87,9 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID")
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
material_set_item_profile_attributes: CollectionProperty(name="Material Set Item Profile Attributes", type=Attribute)
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
)
@@ -1,5 +1,6 @@
from bpy.types import Panel
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
@@ -46,6 +47,8 @@ class BIM_PT_object_material(Panel):
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
if not ProfileData.is_loaded:
ProfileData.load(self.file)
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials:
@@ -79,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]
@@ -172,6 +182,39 @@ class BIM_PT_object_material(Panel):
row.prop(attribute, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if self.set_item_name == "profile":
self.draw_assign_profile_ui(box, item)
self.draw_editable_profile_ui(box, item)
def draw_assign_profile_ui(self, layout, item):
row = layout.row(align=True)
row.prop(self.props, "profile_classes", text="")
if self.props.profile_classes == "IfcParameterizedProfileDef":
row.prop(self.props, "parameterized_profile_classes", text="")
op = row.operator("bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text="")
op.ifc_class = self.props.parameterized_profile_classes
op.material_profile = item["id"]
else:
# TODO: support non parametric profiles by showing a list of named profiles to select from, or an
# eyedropper to pick profile geometry from the scene
row.operator("bim.disable_editing_material_set_item", icon="X", text="")
def draw_editable_profile_ui(self, layout, item):
for attribute in self.props.material_set_item_profile_attributes:
row = layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_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 == "boolean":
row.prop(attribute, "bool_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_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
item = Data.materials[set_item_id]
@@ -84,6 +84,8 @@ class PieAddOpening(bpy.types.Operator):
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
opening_name = obj.name
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
opening_name = obj.children[0].name
else:
opj_name = obj.name
bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
@@ -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:
@@ -3,9 +3,21 @@ from . import ui, prop, operator
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):
@@ -53,6 +52,30 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
class CreateProjectLibrary(bpy.types.Operator):
bl_idname = "bim.create_project_library"
bl_label = "Create Project Library"
def execute(self, context):
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
"project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
)
self.file = IfcStore.get_file()
if self.file.schema == "IFC2X3":
bpy.ops.bim.add_person()
bpy.ops.bim.add_organisation()
project_library = bpy.data.objects.new("My Project Library", None)
bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary")
bpy.ops.bim.assign_unit()
return {"FINISHED"}
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
@@ -64,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
@@ -63,3 +63,71 @@ class BIM_PT_project(Panel):
row.prop(props, "volume_unit", text="Volume Unit")
row = self.layout.row()
row.operator("bim.create_project")
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
@@ -153,6 +153,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:
@@ -9,14 +9,12 @@ classes = (
operator.RemoveWorkPlan,
operator.EnableEditingWorkPlan,
operator.DisableEditingWorkPlan,
operator.LoadWorkSchedules,
operator.DisableWorkScheduleEditingUI,
operator.AddWorkSchedule,
operator.EditWorkSchedule,
operator.RemoveWorkSchedule,
operator.EnableEditingWorkSchedule,
operator.EnableEditingTasks,
operator.DisableEditingWorkSchedule,
operator.LoadTasks,
operator.DisableTaskEditingUI,
operator.LoadWorkCalendars,
operator.DisableWorkCalendarEditingUI,
@@ -25,32 +23,57 @@ classes = (
operator.RemoveWorkCalendar,
operator.EnableEditingWorkCalendar,
operator.DisableEditingWorkCalendar,
operator.AddTask,
operator.AddSummaryTask,
operator.ExpandTask,
operator.ContractTask,
operator.RemoveTask,
operator.EnableEditingTask,
operator.DisableEditingTask,
operator.EditTask,
operator.AssignPredecessor,
operator.AssignSuccessor,
operator.UnassignPredecessor,
operator.UnassignSuccessor,
operator.EnableEditingTaskTime,
operator.DisableEditingTaskTime,
operator.EditTaskTime,
operator.AssignProduct,
operator.UnassignProduct,
operator.GenerateGanttChart,
operator.ImportP6,
operator.LoadTaskProperties,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.WorkSchedule,
prop.Task,
prop.BIMWorkScheduleProperties,
prop.BIMTaskTreeProperties,
prop.WorkCalendar,
prop.BIMWorkCalendarProperties,
prop.Task,
prop.BIMTaskProperties,
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)
@@ -1,7 +1,14 @@
import os
import bpy
import json
import time
import pystache
import webbrowser
import ifcopenshell.api
from datetime import datetime
from dateutil import parser
from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.sequence.data import Data
@@ -61,7 +68,7 @@ class EditWorkPlan(bpy.types.Operator):
ifcopenshell.api.run(
"sequence.edit_work_plan",
self.file,
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes}
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
@@ -123,32 +130,6 @@ class DisableEditingWorkPlan(bpy.types.Operator):
return {"FINISHED"}
class LoadWorkSchedules(bpy.types.Operator):
bl_idname = "bim.load_work_schedules"
bl_label = "Load Work Schedules"
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
while len(props.work_schedules) > 0:
props.work_schedules.remove(0)
for ifc_definition_id, work_schedule in Data.work_schedules.items():
new = props.work_schedules.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_schedule["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_schedule()
return {"FINISHED"}
class DisableWorkScheduleEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_schedule_editing_ui"
bl_label = "Disable WorkSchedule Editing UI"
def execute(self, context):
context.scene.BIMWorkScheduleProperties.is_editing = False
return {"FINISHED"}
class AddWorkSchedule(bpy.types.Operator):
bl_idname = "bim.add_work_schedule"
bl_label = "Add Work Schedule"
@@ -156,7 +137,6 @@ class AddWorkSchedule(bpy.types.Operator):
def execute(self, context):
ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_schedules()
return {"FINISHED"}
@@ -179,10 +159,10 @@ class EditWorkSchedule(bpy.types.Operator):
ifcopenshell.api.run(
"sequence.edit_work_schedule",
self.file,
**{"work_schedule": self.file.by_id(props.active_work_schedule_id), "attributes": attributes}
**{"work_schedule": self.file.by_id(props.active_work_schedule_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_schedules()
bpy.ops.bim.disable_editing_work_schedule()
return {"FINISHED"}
@@ -194,10 +174,9 @@ class RemoveWorkSchedule(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_schedule", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}
"sequence.remove_work_schedule", self.file, work_schedule=self.file.by_id(self.work_schedule)
)
Data.load(self.file)
bpy.ops.bim.load_work_schedules()
return {"FINISHED"}
@@ -207,17 +186,22 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
work_schedule: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
while len(props.work_schedule_attributes) > 0:
props.work_schedule_attributes.remove(0)
self.props = context.scene.BIMWorkScheduleProperties
self.props.active_work_schedule_id = self.work_schedule
while len(self.props.work_schedule_attributes) > 0:
self.props.work_schedule_attributes.remove(0)
self.enable_editing_work_schedule()
self.props.is_editing = "WORK_SCHEDULE"
return {"FINISHED"}
def enable_editing_work_schedule(self):
data = Data.work_schedules[self.work_schedule]
for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.work_schedule_attributes.add()
new = self.props.work_schedule_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
@@ -230,9 +214,77 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_work_schedule_id = self.work_schedule
class EnableEditingTasks(bpy.types.Operator):
bl_idname = "bim.enable_editing_tasks"
bl_label = "Enable Editing Tasks"
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.tprops = context.scene.BIMTaskTreeProperties
self.props.active_work_schedule_id = self.work_schedule
while len(self.tprops.tasks) > 0:
self.tprops.tasks.remove(0)
self.contracted_tasks = json.loads(self.props.contracted_tasks)
for related_object_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
self.create_new_task_li(related_object_id, 0)
bpy.ops.bim.load_task_properties()
self.props.is_editing = "TASKS"
return {"FINISHED"}
def create_new_task_li(self, related_object_id, level_index):
task = Data.tasks[related_object_id]
new = self.tprops.tasks.add()
new.ifc_definition_id = related_object_id
new.is_expanded = related_object_id not in self.contracted_tasks
new.level_index = level_index
if task["RelatedObjects"]:
new.has_children = True
if new.is_expanded:
for related_object_id in task["RelatedObjects"]:
self.create_new_task_li(related_object_id, level_index + 1)
return {"FINISHED"}
class LoadTaskProperties(bpy.types.Operator):
bl_idname = "bim.load_task_properties"
bl_label = "Load Task Properties"
task: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.tprops = context.scene.BIMTaskTreeProperties
self.props.is_task_update_enabled = False
for item in self.tprops.tasks:
if self.task and item.ifc_definition_id != self.task:
continue
task = Data.tasks[item.ifc_definition_id]
item.name = task["Name"] or "Unnamed"
item.identification = task["Identification"] or "XXX"
if self.props.active_task_id:
item.is_predecessor = self.props.active_task_id in task["IsPredecessorTo"]
item.is_successor = self.props.active_task_id in task["IsSuccessorFrom"]
if task["TaskTime"]:
task_time = Data.task_times[task["TaskTime"]]
item.start = self.canonicalise_time(task_time["ScheduleStart"])
item.finish = self.canonicalise_time(task_time["ScheduleFinish"])
# TODO: duration
item.duration = "-"
else:
item.start = "-"
item.finish = "-"
item.duration = "-"
self.props.is_task_update_enabled = True
return {"FINISHED"}
def canonicalise_time(self, time):
if not time:
return "-"
return time.strftime("%d/%m/%y")
class DisableEditingWorkSchedule(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_schedule"
@@ -243,6 +295,433 @@ class DisableEditingWorkSchedule(bpy.types.Operator):
return {"FINISHED"}
class DisableTaskEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_task_editing_ui"
bl_label = "Disable Task Editing UI"
def execute(self, context):
context.scene.BIMTaskProperties.is_editing = False
return {"FINISHED"}
class AddTask(bpy.types.Operator):
bl_idname = "bim.add_task"
bl_label = "Add Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.add_task", self.file, **{"parent_task": self.file.by_id(self.task)})
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
class AddSummaryTask(bpy.types.Operator):
bl_idname = "bim.add_summary_task"
bl_label = "Add Task"
work_schedule: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.add_task", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)})
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
class ExpandTask(bpy.types.Operator):
bl_idname = "bim.expand_task"
bl_label = "Expand Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
contracted_tasks = json.loads(props.contracted_tasks)
contracted_tasks.remove(self.task)
props.contracted_tasks = json.dumps(contracted_tasks)
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
class ContractTask(bpy.types.Operator):
bl_idname = "bim.contract_task"
bl_label = "Contract Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
contracted_tasks = json.loads(props.contracted_tasks)
contracted_tasks.append(self.task)
props.contracted_tasks = json.dumps(contracted_tasks)
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
class RemoveTask(bpy.types.Operator):
bl_idname = "bim.remove_task"
bl_label = "Remove Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_task",
self.file,
task=IfcStore.get_file().by_id(self.task),
)
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
class EnableEditingTaskTime(bpy.types.Operator):
bl_idname = "bim.enable_editing_task_time"
bl_label = "Enable Editing Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
task_time_id = Data.tasks[self.task]["TaskTime"] or self.add_task_time().id()
while len(props.task_time_attributes) > 0:
props.task_time_attributes.remove(0)
data = Data.task_times[task_time_id]
for attribute in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.task_time_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
if isinstance(data[attribute.name()], datetime):
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
else:
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()]
elif data_type == "float":
new.float_value = 0.0 if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_task_time_id = task_time_id
props.active_task_id = self.task
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
def add_task_time(self):
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.file.by_id(self.task))
Data.load(IfcStore.get_file())
return task_time
class DisableEditingTaskTime(bpy.types.Operator):
bl_idname = "bim.disable_editing_task_time"
bl_label = "Disable Editing Task Time"
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
bpy.ops.bim.disable_editing_task()
return {"FINISHED"}
class EditTaskTime(bpy.types.Operator):
bl_idname = "bim.edit_task_time"
bl_label = "Edit Task Time"
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = {}
for attribute in props.task_time_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "boolean":
attributes[attribute.name] = attribute.bool_value
elif attribute.data_type == "float":
attributes[attribute.name] = attribute.float_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
attributes = self.convert_strings_to_date_times(attributes)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_task_time",
self.file,
**{"task_time": self.file.by_id(props.active_task_time_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_task_time()
bpy.ops.bim.load_task_properties(task=props.active_task_id)
return {"FINISHED"}
def convert_strings_to_date_times(self, attributes):
for key, value in attributes.items():
if not value:
continue
if "Start" in key or "Finish" in key or key == "StatusTime":
try:
attributes[key] = parser.isoparse(value)
except:
try:
attributes[key] = parser.parse(value, dayfirst=True, fuzzy=True)
except:
attributes[key] = None
return attributes
class EnableEditingTask(bpy.types.Operator):
bl_idname = "bim.enable_editing_task"
bl_label = "Enable Editing Task"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
while len(props.task_attributes) > 0:
props.task_attributes.remove(0)
data = Data.tasks[self.task]
for attribute in IfcStore.get_schema().declaration_by_name("IfcTask").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.task_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_task_id = self.task
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
class DisableEditingTask(bpy.types.Operator):
bl_idname = "bim.disable_editing_task"
bl_label = "Disable Editing Task"
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_task_id = 0
return {"FINISHED"}
class EditTask(bpy.types.Operator):
bl_idname = "bim.edit_task"
bl_label = "Edit Task"
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = {}
for attribute in props.task_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "boolean":
attributes[attribute.name] = attribute.bool_value
elif attribute.data_type == "integer":
attributes[attribute.name] = attribute.int_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_task", self.file, **{"task": self.file.by_id(props.active_task_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_task()
bpy.ops.bim.load_task_properties(task=props.active_task_id)
return {"FINISHED"}
class AssignPredecessor(bpy.types.Operator):
bl_idname = "bim.assign_predecessor"
bl_label = "Assign Predecessor"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_sequence",
self.file,
relating_process=IfcStore.get_file().by_id(self.task),
related_process=IfcStore.get_file().by_id(props.active_task_id),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
return {"FINISHED"}
class AssignSuccessor(bpy.types.Operator):
bl_idname = "bim.assign_successor"
bl_label = "Assign Successor"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_sequence",
self.file,
relating_process=IfcStore.get_file().by_id(props.active_task_id),
related_process=IfcStore.get_file().by_id(self.task),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
return {"FINISHED"}
class UnassignPredecessor(bpy.types.Operator):
bl_idname = "bim.unassign_predecessor"
bl_label = "Unassign Predecessor"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_sequence",
self.file,
relating_process=IfcStore.get_file().by_id(self.task),
related_process=IfcStore.get_file().by_id(props.active_task_id),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
return {"FINISHED"}
class UnassignSuccessor(bpy.types.Operator):
bl_idname = "bim.unassign_successor"
bl_label = "Unassign Successor"
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_sequence",
self.file,
relating_process=self.file.by_id(props.active_task_id),
related_process=self.file.by_id(self.task),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
return {"FINISHED"}
class AssignProduct(bpy.types.Operator):
bl_idname = "bim.assign_product"
bl_label = "Assign Product"
task: bpy.props.IntProperty()
related_product: bpy.props.StringProperty()
def execute(self, context):
related_products = (
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
)
for related_product in related_products:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_product",
self.file,
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
class UnassignProduct(bpy.types.Operator):
bl_idname = "bim.unassign_product"
bl_label = "Unassign Product"
task: bpy.props.IntProperty()
related_product: bpy.props.StringProperty()
def execute(self, context):
related_products = (
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
)
for related_product in related_products:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_product",
self.file,
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
class GenerateGanttChart(bpy.types.Operator):
bl_idname = "bim.generate_gantt_chart"
bl_label = "Generate Gantt Chart"
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
self.json = []
for task_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
self.create_new_task_json(task_id)
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
f.write(pystache.render(t.read(), {"json_data": json.dumps(self.json)}))
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
return {"FINISHED"}
def create_new_task_json(self, task_id):
task = self.file.by_id(task_id)
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,
}
)
for task_id in Data.tasks[task_id]["RelatedObjects"]:
self.create_new_task_json(task_id)
class LoadWorkCalendars(bpy.types.Operator):
bl_idname = "bim.load_work_calendars"
bl_label = "Load Work Calendars"
@@ -299,7 +778,7 @@ class EditWorkCalendar(bpy.types.Operator):
ifcopenshell.api.run(
"sequence.edit_work_calendar",
self.file,
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes}
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_calendars()
@@ -361,27 +840,21 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
return {"FINISHED"}
class LoadTasks(bpy.types.Operator):
bl_idname = "bim.load_tasks"
bl_label = "Load Tasks"
class ImportP6(bpy.types.Operator, ImportHelper):
bl_idname = "import_p6.bim"
bl_label = "Import P6"
filename_ext = ".xml"
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
def execute(self, context):
props = context.scene.BIMTaskProperties
while len(props.tasks) > 0:
props.tasks.remove(0)
for ifc_definition_id, task in Data.tasks.items():
new = props.tasks.add()
new.ifc_definition_id = ifc_definition_id
new.name = task["Name"]
new.identification = task["Identification"]
props.is_editing = True
return {"FINISHED"}
class DisableTaskEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_task_editing_ui"
bl_label = "Disable Task Editing UI"
def execute(self, context):
context.scene.BIMTaskProperties.is_editing = False
from ifcp6.p62ifc import P62Ifc
self.file = IfcStore.get_file()
start = time.time()
p62ifc = P62Ifc()
p62ifc.xml = self.filepath
p62ifc.file = self.file
p62ifc.work_plan = self.file.by_type("IfcWorkPlan")[0]
p62ifc.execute()
Data.load(IfcStore.get_file())
print("Import finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"}
@@ -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,108 @@ 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))
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)
is_predecessor: BoolProperty(name="Is Predecessor")
is_successor: BoolProperty(name="Is Successor")
class WorkPlan(PropertyGroup):
@@ -38,17 +134,25 @@ class BIMWorkPlanProperties(PropertyGroup):
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")
class BIMWorkScheduleProperties(PropertyGroup):
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)
is_editing: StringProperty(name="Is Editing")
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_times: BoolProperty(name="Should Show Times", default=True)
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)
class BIMTaskTreeProperties(PropertyGroup):
# This belongs by itself for performance reasons.
# 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):
@@ -80,31 +80,44 @@ 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.is_editing == "WORK_SCHEDULE":
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "TASKS":
row.prop(self.props, "should_show_times", text="", icon="TIME")
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.is_editing == "WORK_SCHEDULE":
self.draw_editable_work_schedule_ui()
elif self.props.is_editing == "TASKS":
self.draw_editable_task_ui(work_schedule_id)
if self.props.active_work_schedule_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
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 +127,119 @@ 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:
self.draw_editable_task_attributes_ui()
if self.props.active_task_time_id:
self.draw_editable_task_time_attributes_ui()
class BIM_UL_work_schedules(UIList):
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:
row.prop(item, "start", emboss=False, text="")
row.prop(item, "finish", emboss=False, text="")
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.active_task_time_id:
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
else:
row.operator("bim.edit_task", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
elif props.active_task_id:
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_time", text="", icon="TIME").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):
@@ -193,53 +304,3 @@ class BIM_UL_work_calendars(UIList):
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")
else:
row.operator("bim.load_tasks", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_tasks",
"",
self.props,
"tasks",
self.props,
"active_task_index",
)
if self.props.active_task_index:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
pass
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)
@@ -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,12 +20,16 @@ classes = (
operator.EnableEditingStructuralConnectionCondition,
operator.DisableEditingStructuralConnectionCondition,
operator.RemoveStructuralConnectionCondition,
operator.EnableEditingStructuralMemberAxis,
operator.DisableEditingStructuralMemberAxis,
operator.EditStructuralMemberAxis,
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,
)
@@ -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,74 @@ 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"}
@@ -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")
@@ -33,3 +47,6 @@ class BIMObjectStructuralProperties(PropertyGroup):
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)
@@ -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"
@@ -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()
@@ -27,6 +27,8 @@ class BIM_PT_voids(Panel):
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
op.opening = obj.name
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
op.opening = obj.children[0].name
else:
op.obj = obj.name
+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
-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.")
@@ -1008,7 +1011,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);
}
@@ -21,7 +21,12 @@ class Usecase:
context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin)
else:
context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
project = self.file.by_type("IfcProject")[0]
if self.file.schema == "IFC2X3":
project = self.file.by_type("IfcProject")[0]
else:
project = self.file.by_type("IfcContext")[0]
if project.RepresentationContexts:
contexts = list(project.RepresentationContexts)
else:
@@ -0,0 +1,43 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_control": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["related_object"].HasAssignments:
for assignment in self.settings["related_object"].HasAssignments:
if (
assignment.is_a("IfclRelAssignsToControl")
and assignment.RelatingControl == self.settings["relating_control"]
):
return
controls = None
if self.settings["relating_control"].Controls:
controls = self.settings["relating_control"].Controls[0]
if controls:
related_objects = list(controls.RelatedObjects)
related_objects.append(self.settings["related_object"])
controls.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls})
else:
controls = self.file.create_entity(
"IfcRelAssignsToControl",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["related_object"]],
"RelatingControl": self.settings["relating_control"],
},
)
return controls
@@ -0,0 +1,25 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_control": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != self.settings["relating_control"]:
continue
if len(rel.RelatedObjects) == 1:
return self.file.remove(rel)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["related_object"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
@@ -0,0 +1,28 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_schedule": None, "cost_item": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
cost_item = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCostItem")
if self.settings["cost_schedule"]:
self.file.create_entity(
"IfcRelAssignsToControl",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [cost_item],
"RelatingControl": self.settings["cost_schedule"],
}
)
elif self.settings["cost_item"]:
ifcopenshell.api.run(
"nest.assign_object", self.file, related_object=cost_item, relating_object=self.settings["cost_item"]
)
return cost_item
@@ -4,15 +4,19 @@ import ifcopenshell.util.date
class Data:
is_loaded = False
cost_schedules = {}
cost_items = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.cost_schedules = {}
cls.cost_items = {}
@classmethod
def load(cls, file):
cls.cost_schedules = {}
cls.cost_items = {}
for cost_schedule in file.by_type("IfcCostSchedule"):
data = cost_schedule.get_info()
del data["OwnerHistory"]
@@ -20,5 +24,24 @@ class Data:
data["SubmittedOn"] = ifcopenshell.util.date.ifc2datetime(data["SubmittedOn"])
if data["UpdateDate"]:
data["UpdateDate"] = ifcopenshell.util.date.ifc2datetime(data["UpdateDate"])
data["RelatedObjects"] = []
for rel in cost_schedule.Controls:
for related_object in rel.RelatedObjects:
if related_object.is_a("IfcCostItem"):
data["RelatedObjects"].append(related_object.id())
break # We are only allowed one summary cost item
cls.cost_schedules[cost_schedule.id()] = data
for cost_item in file.by_type("IfcCostItem"):
data = cost_item.get_info()
del data["OwnerHistory"]
del data["CostValues"]
del data["CostQuantities"]
data["RelatedObjects"] = []
data["Controls"] = []
for rel in cost_item.IsNestedBy:
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
for rel in cost_item.Controls:
[data["Controls"].append(o.id()) for o in rel.RelatedObjects or []]
cls.cost_items[cost_item.id()] = data
cls.is_loaded=True
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["cost_item"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_schedule": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["cost_schedule"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"cost_item": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
# TODO: do a deep purge
self.file.remove(self.settings["cost_item"])
@@ -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"])
@@ -0,0 +1,14 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"material_profile": None, "profile": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if (
self.settings["material_profile"].Profile
and len(self.file.get_inverse(self.settings["material_profile"].Profile)) == 1
):
self.file.remove(self.settings["material_profile"].Profile)
self.settings["material_profile"].Profile = self.settings["profile"]
@@ -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()}
@@ -4,6 +4,7 @@ class Usecase():
self.settings = {
"profile": None,
"attributes": {},
"profile_attributes": {},
"material": None
}
for key, value in settings.items():
@@ -13,3 +14,5 @@ class Usecase():
for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value)
self.settings["profile"].Material = self.settings["material"]
for name, value in self.settings["profile_attributes"].items():
setattr(self.settings["profile"].Profile, name, value)
@@ -0,0 +1,53 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"related_object": None,
"relating_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
nests = None
if self.settings["related_object"].Nests:
nests = self.settings["related_object"].Nests[0]
is_nested_by = None
for rel in self.settings["relating_object"].IsNestedBy:
if rel.is_a("IfcRelNests"):
is_nested_by = rel
break
if nests and nests == is_nested_by:
return
if nests:
related_objects = list(nests.RelatedObjects)
related_objects.remove(self.settings["related_object"])
if related_objects:
nests.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": nests})
else:
self.file.remove(nests)
if is_nested_by:
related_objects = list(is_nested_by.RelatedObjects)
related_objects.append(self.settings["related_object"])
is_nested_by.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_nested_by})
else:
is_nested_by = self.file.create_entity(
"IfcRelNests",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["related_object"]],
"RelatingObject": self.settings["relating_object"],
}
)
return is_nested_by
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"ifc_class": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity(self.settings["ifc_class"])
@@ -0,0 +1,17 @@
class Data:
is_loaded = False
profiles = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.profiles = {}
@classmethod
def load(cls, file):
if not file:
return
cls.profiles = {}
for profile in file.by_type("IfcProfileDef"):
cls.profiles[profile.id()] = profile.get_info()
cls.is_loaded = True
@@ -0,0 +1,54 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"element": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
element = self.file.add(self.settings["element"])
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
added_contexts = [e for e in self.file.traverse(element) if e.is_a("IfcGeometricRepresentationContext")]
for added_context in added_contexts:
equivalent_existing_context = self.get_equivalent_existing_context(added_context)
if not equivalent_existing_context:
equivalent_existing_context = self.create_equivalent_context(added_context)
for inverse in self.file.get_inverse(added_context):
ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context)
for added_context in added_contexts:
if added_context.is_a() == "IfcGeometricRepresentationContext":
ifcopenshell.util.element.remove_deep(self.file, added_context)
return element
def get_equivalent_existing_context(self, added_context):
for context in self.existing_contexts:
if context.is_a() != added_context.is_a():
continue
if context.is_a("IfcGeometricRepresentationSubContext"):
if (
context.ContextType == added_context.ContextType
and context.ContextIdentifier == added_context.ContextIdentifier
and context.TargetView == added_context.TargetView
):
return context
elif (
context.ContextType == added_context.ContextType
and context.ContextIdentifier == added_context.ContextIdentifier
):
return context
def create_equivalent_context(self, added_context):
if added_context.is_a("IfcGeometricRepresentationSubContext"):
return ifcopenshell.api.run(
"context.add_context",
context=added_context.ContextType,
subcontext=added_context.ContextIdentifier,
target_view=added_context.TargetView,
)
return ifcopenshell.api.run(
"context.add_context", context=added_context.ContextType, subcontext=added_context.ContextIdentifier
)
@@ -0,0 +1,39 @@
import ifcopenshell.api
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"work_schedule": None,
"parent_task": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
task = ifcopenshell.api.run(
"root.create_entity",
self.file,
ifc_class="IfcTask",
name=None,
predefined_type="NOTDEFINED",
identification="none",
)
task.IsMilestone = False
if self.settings["work_schedule"]:
self.file.create_entity(
"IfcRelAssignsToControl",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [task],
"RelatingControl": self.settings["work_schedule"],
}
)
elif self.settings["parent_task"]:
ifcopenshell.api.run(
"nest.assign_object", self.file, related_object=task, relating_object=self.settings["parent_task"]
)
return task
@@ -0,0 +1,17 @@
import ifcopenshell.util.date
from datetime import datetime
from datetime import timedelta
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"task": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
task_time = self.file.create_entity("IfcTaskTime")
self.settings["task"].TaskTime = task_time
return task_time
@@ -0,0 +1,43 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_product": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["related_object"].HasAssignments:
for assignment in self.settings["related_object"].HasAssignments:
if (
assignment.is_a("IfclRelAssignsToProduct")
and assignment.RelatingProduct == self.settings["relating_product"]
):
return
referenced_by = None
if self.settings["relating_product"].ReferencedBy:
referenced_by = self.settings["relating_product"].ReferencedBy[0]
if referenced_by:
related_objects = list(referenced_by.RelatedObjects)
related_objects.append(self.settings["related_object"])
referenced_by.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": referenced_by})
else:
referenced_by = self.file.create_entity(
"IfcRelAssignsToProduct",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["related_object"]],
"RelatingProduct": self.settings["relating_product"],
}
)
return referenced_by
@@ -0,0 +1,27 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_process": None,
"related_process": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_process"].IsSuccessorFrom or []:
if rel.RelatingProcess == self.settings["relating_process"]:
return rel
return self.file.create_entity(
"IfcRelSequence",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingProcess": self.settings["relating_process"],
"RelatedProcess": self.settings["related_process"],
}
)
@@ -6,6 +6,7 @@ class Data:
work_plans = {}
work_schedules = {}
tasks = {}
task_times = {}
@classmethod
def purge(cls):
@@ -14,6 +15,7 @@ class Data:
cls.work_schedules = {}
cls.work_calendars = {}
cls.tasks = {}
cls.task_times = {}
@classmethod
def load(cls, file):
@@ -24,6 +26,7 @@ class Data:
cls.load_work_schedules()
cls.load_work_calendars()
cls.load_tasks()
cls.load_task_times()
cls.is_loaded = True
@classmethod
@@ -52,10 +55,16 @@ 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"]
@@ -67,4 +76,34 @@ class Data:
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["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.RelatedProcess.id()) for rel in task.IsPredecessorTo or []]
[data["IsSuccessorFrom"].append(rel.RelatingProcess.id()) for rel in task.IsSuccessorFrom or []]
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)
# TODO parse duration
cls.task_times[task_time.id()] = data
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"task": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["task"], name, value)
@@ -0,0 +1,16 @@
import ifcopenshell.util.date
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"task_time": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
if "Start" in name or "Finish" in name or name == "StatusTime":
if value:
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
setattr(self.settings["task_time"], name, value)
@@ -0,0 +1,19 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"task": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
# TODO: do a deep purge
ifcopenshell.api.run(
"project.unassign_declaration",
self.file,
definition=self.settings["task"],
relating_context=self.file.by_type("IfcContext")[0],
)
self.file.remove(self.settings["task"])
@@ -1,3 +1,6 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -0,0 +1,25 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_product": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != self.settings["relating_product"]:
continue
if len(rel.RelatedObjects) == 1:
return self.file.remove(rel)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["related_object"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
@@ -0,0 +1,18 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_process": None,
"related_process": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_process"].IsSuccessorFrom or []:
if rel.RelatingProcess == self.settings["relating_process"]:
self.file.remove(rel)
@@ -0,0 +1,11 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"structural_member": None, "axis": [0.0, 0.0, 1.0]}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.file.get_inverse(self.settings["structural_member"].Axis) == 1:
self.file.remove(self.settings["structural_member"].Axis)
self.settings["structural_member"].Axis = self.file.createIfcDirection(self.settings["axis"])
+77 -21
View File
@@ -64,7 +64,11 @@ class facet(metaclass=meta_facet):
yield k, getattr(self, k)
def __str__(self):
return self.message % dict(list(self))
di = dict(list(self))
for k, v in di.items():
if isinstance(v, str) and not len(v):
di[k] = "not specified"
return self.message % di
class entity(facet):
@@ -88,19 +92,19 @@ class classification(facet):
"""
parameters = ["system", "value"]
message = "a classification reference to '%(value)s' from '%(system)s'"
message = "a classification reference '%(value)s' from '%(system)s'"
def __call__(self, inst, logger):
refs = []
for association in inst.HasAssociations:
if association.is_a("IfcRelAssociatesClassification"):
cref = association.RelatingClassification
refs.append((cref.ReferencedSource, cref.Name))
refs.append((cref.ReferencedSource.Name, cref.ItemReference))
return facet_evaluation(
(self.system, self.value) in refs,
# @todo
"",
"[classification_eval_todo]",
)
@@ -109,17 +113,19 @@ class property(facet):
The IDS property facet implenented using `ifcopenshell.util.element`
"""
parameters = ["property", "propertyset", "value"]
message = "a property '%(property)s' in '%(propertyset)s' with value '%(value)s'"
parameters = ["name", "propertyset", "value"]
# import pdb;pdb.set_trace()
message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'"
def __call__(self, inst, logger):
props = ifcopenshell.util.element.get_psets(inst)
pset = props.get(self.propertyset)
val = pset.get(self.property) if pset else None
val = pset.get(self.name) if pset else None
logger.debug("Testing %s == %s", val, self.value)
di = {
"property": self.property,
"name": self.name,
"propertyset": self.propertyset,
"value": val,
}
@@ -128,13 +134,38 @@ class property(facet):
msg = self.message % di
else:
if pset:
msg = "a set '%(propertyset)s', but no property '%(property)'" % di
msg = "a set '%(propertyset)s', but no property '%(name)'" % di
else:
msg = "no set '%(propertyset)s'" % di
return facet_evaluation(val == self.value, msg)
class material(facet):
"""
The IDS material facet
"""
parameters = ["name", "value"]
message = "a material '%(name)s with value '%(value)s'"
def __call__(self, inst, logger):
material_relations = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")]
names = []
for rel in material_relations:
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
"[material_eval_todo]",
)
class boolean_logic:
"""
Boolean conjunction over a collection of functions
@@ -166,17 +197,39 @@ class restriction:
"""
def __init__(self, node):
self.options = [
n.getAttribute("value")
for n in node.childNodes
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration")
]
self.restriction_on = node.getAttribute("base")
self.options = []
self.type = []
for n in node.childNodes:
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"):
self.options.append(n.getAttribute("value"))
self.type = "enumeration"
elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")):
self.options.append(n.getAttribute("value"))
self.type = "bounds"
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"):
self.options.append(n.getAttribute("value"))
self.type = "length"
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
def __repr__(self):
return " or ".join(self.options)
if self.type == "enumeration":
return " or ".join(self.options)
elif self.type == "bounds":
self.options.sort()
return "of type %s, having a value between %s and %s" % (self.restriction_on, self.options[0], self.options[1])
elif self.type == "length":
return "of type %s with a length of %s" % (self.restriction_on, self.options[0])
elif self.type == "pattern":
return "of type %s respecting pattern %s" % (self.restriction_on, self.options[0])
class specification:
@@ -202,10 +255,11 @@ class specification:
def __call__(self, inst, logger):
if self.applicabiliy(inst, logger):
valid = self.requirements(inst, logger)
if valid:
logger.info(str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant")
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant"})
else:
logger.error(str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant")
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant"})
def __str__(self):
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
@@ -229,15 +283,17 @@ class ids:
for spec in self.specifications:
for elem in ifc_file.by_type("IfcObject"):
spec(elem, logger)
if __name__ == "__main__":
import sys
import sys, os
import logging
import ifcopenshell
filename = os.path.join(os.getcwd(), "ids.txt")
logger = logging.getLogger("IDS")
logging.basicConfig(level=logging.INFO, format="%(message)s")
logging.basicConfig(filename=filename, level=logging.INFO, format="%(message)s")
logging.FileHandler(filename, mode='w')
ids_file = ids(sys.argv[1])
ifc_file = ifcopenshell.open(sys.argv[2])
@@ -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()
+114
View File
@@ -0,0 +1,114 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.date
import xml.etree.ElementTree as ET
from datetime import datetime
class P62Ifc:
def __init__(self):
self.xml = None
self.file = None
self.work_plan = None
self.project = {}
self.wbs = {}
self.activity = {}
def execute(self):
self.parse_xml()
self.create_ifc()
def parse_xml(self):
tree = ET.parse(self.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,
"Code": wbs.find("pr:Code", 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": datetime.fromisoformat(activity.find("pr:StartDate", ns).text),
"FinishDate": datetime.fromisoformat(activity.find("pr:FinishDate", ns).text),
"Status": activity.find("pr:Status", ns).text,
"ifc": None,
}
)
def get_wbs(self, wbs):
return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []}
def create_ifc(self):
if not self.file:
self.file = self.create_boilerplate_ifc()
work_schedule = self.create_work_schedule()
self.create_tasks(work_schedule)
def create_work_schedule(self):
return ifcopenshell.api.run(
"sequence.add_work_schedule", self.file, name=self.project["Name"], work_plan=self.work_plan
)
def create_tasks(self, work_schedule):
for wbs in self.wbs.values():
self.create_task_from_wbs(wbs, work_schedule)
def create_task_from_wbs(self, wbs, work_schedule):
wbs["ifc"] = ifcopenshell.api.run(
"sequence.add_task",
self.file,
work_schedule=None if wbs["ParentObjectId"] else work_schedule,
parent_task=self.wbs[wbs["ParentObjectId"]]["ifc"] if wbs["ParentObjectId"] else None,
)
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
task=wbs["ifc"],
attributes={"Name": wbs["Name"], "Identification": wbs["Code"]},
)
for activity in wbs["activities"]:
self.create_task_from_activity(activity, wbs, work_schedule)
def create_task_from_activity(self, activity, wbs, work_schedule):
activity["ifc"] = ifcopenshell.api.run(
"sequence.add_task",
self.file,
parent_task=wbs["ifc"],
)
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
task=activity["ifc"],
attributes={
"Name": activity["Name"],
"Identification": activity["Identification"],
"Status": activity["Status"],
"IsMilestone": activity["StartDate"] == activity["FinishDate"],
},
)
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=activity["ifc"])
ifcopenshell.api.run(
"sequence.edit_task_time",
self.file,
task_time=task_time,
attributes={
"ScheduleStart": activity["StartDate"],
"ScheduleFinish": activity["FinishDate"],
},
)
def create_boilerplate_ifc(self):
self.file = ifcopenshell.file(schema="IFC4")
self.work_plan = self.file.create_entity("IfcWorkPlan")
+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;
-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;
}
}
}
+44 -6
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;
@@ -603,14 +603,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 +662,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 {
@@ -1408,16 +1433,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_;
}
@@ -1708,6 +1739,8 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
}
void SvgSerializer::finalize() {
doWriteHeader();
for (auto& p : drawing_metadata) {
addTextAnnotations(p.first);
}
@@ -1900,6 +1933,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\"";
+15 -3
View File
@@ -136,16 +136,17 @@ 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_;
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 +189,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 +203,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 +253,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;