Merge branch 'v0.7.0' into new_covering_tools

This commit is contained in:
Massimo Fabbro
2023-09-25 15:01:03 +02:00
committed by GitHub
141 changed files with 2432 additions and 3537 deletions
+2 -10
View File
@@ -201,8 +201,8 @@ endif
cp -r dist/working/IfcOpenShell-0.7.0/src/ifctester/ifctester dist/blenderbim/libs/site/packages/
# Provides IFCFM functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcfm/ifcfm dist/blenderbim/libs/site/packages/
# Provides IFCCOBie functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
# Provides bSDD functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/bsdd/* dist/blenderbim/libs/site/packages/
# Provides IFCDiff functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcdiff/* dist/blenderbim/libs/site/packages/
# Provides IFCCSV functionality
@@ -594,14 +594,6 @@ endif
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCOBie for XLSX support
# TODO: see if we can replace this with openpyxl which does both read/write
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
cd dist/working && tar -xzvf XlsxWriter*
cd dist/working/XlsxWriter-1.2.9/ && cp -r xlsxwriter ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by augin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
+1 -1
View File
@@ -30,6 +30,7 @@ modules = {
"project": None,
"search": None,
"bcf": None,
"bsdd": None,
"root": None,
"unit": None,
"model": None,
@@ -45,7 +46,6 @@ modules = {
"aggregate": None,
"geometry": None,
"fm": None,
"cobie": None,
"resource": None,
"cost": None,
"sequence": None,
+1 -1
View File
@@ -325,7 +325,7 @@ def draw_filter(layout, props, data, module):
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
elif ifc_filter.type == "location":
row = box.row(align=True)
row.prop(ifc_filter, "name", text="", icon="PACKAGE")
row.prop(ifc_filter, "value", text="", icon="PACKAGE")
elif ifc_filter.type == "query":
row = box.row(align=True)
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
+42 -5
View File
@@ -20,6 +20,7 @@ import os
import re
import bpy
import time
import json
import bmesh
import logging
import mathutils
@@ -145,12 +146,15 @@ class MaterialCreator:
faces_remap = None
texture_map = None
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
for tex_coord_index in coordinates.TexCoordIndices]
faces_remap = [
[coordinates_remap[i - 1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
for tex_coord_index in coordinates.TexCoordIndices
]
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
elif coordinates.is_a("IfcIndexedTriangleTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in triangle_face]
for triangle_face in coordinates.MappedTo.CoordIndex]
faces_remap = [
[coordinates_remap[i - 1] for i in triangle_face] for triangle_face in coordinates.MappedTo.CoordIndex
]
texture_map = coordinates.TexCoordIndex
# apply uv to each face
@@ -165,7 +169,7 @@ class MaterialCreator:
)
# apply uv to each loop
for loop, i in zip(bface.loops, texCoordIndex):
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i-1]
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1]
# Finish up, write the bmesh back to the mesh
bm.to_mesh(self.mesh)
@@ -262,6 +266,8 @@ class IfcImporter:
self.profile_code("Calculate unit scale")
self.calculate_model_offset()
self.profile_code("Calculate model offset")
self.predict_dense_mesh()
self.profile_code("Predict dense mesh")
self.set_units()
self.profile_code("Set units")
self.create_project()
@@ -306,6 +312,7 @@ class IfcImporter:
self.set_default_context()
self.profile_code("Setting default context")
self.setup_viewport_camera()
self.setup_arrays()
self.update_progress(100)
bpy.context.window_manager.progress_end()
@@ -346,6 +353,7 @@ class IfcImporter:
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
self.settings_body_2d.set_context_ids(self.body_contexts)
# Annotation ContextType is to accommodate broken Revit files
# See https://github.com/Autodesk/revit-ifc/issues/187
self.plan_contexts = [
@@ -496,6 +504,26 @@ class IfcImporter:
products.extend(self.get_products_from_shape_representation(inverse_element))
return products
def predict_dense_mesh(self):
threshold = 10000 # Just from experience.
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
if self.file.schema == "IFC2X3":
return
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
def calculate_model_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
@@ -1908,6 +1936,15 @@ class IfcImporter:
bpy.ops.view3d.view_selected()
bpy.ops.object.select_all(action="DESELECT")
def setup_arrays(self):
for element in self.file.by_type("IfcElement"):
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset_data or not pset_data.get("Data", None): # skip array children
continue
for i in range(len(json.loads(pset_data["Data"]))):
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
class IfcImportSettings:
def __init__(self):
@@ -202,6 +202,7 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj in context.selectable_objects:
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
return {"FINISHED"}
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -20,17 +20,23 @@ import bpy
from . import ui, prop, operator
classes = (
operator.SelectCobieIfcFile,
operator.SelectCobieJsonFile,
operator.ExecuteIfcCobie,
prop.COBieProperties,
ui.BIM_PT_cobie,
operator.GetBSDDClassificationProperties,
operator.LoadBSDDDomains,
operator.SearchBSDDClassifications,
operator.SetActiveBSDDDomain,
prop.BSDDDomain,
prop.BSDDClassification,
prop.BSDDPset,
prop.BIMBSDDProperties,
ui.BIM_UL_bsdd_domains,
ui.BIM_UL_bsdd_classifications,
ui.BIM_PT_bsdd,
)
def register():
bpy.types.Scene.COBieProperties = bpy.props.PointerProperty(type=prop.COBieProperties)
bpy.types.Scene.BIMBSDDProperties = bpy.props.PointerProperty(type=prop.BIMBSDDProperties)
def unregister():
del bpy.types.Scene.COBieProperties
del bpy.types.Scene.BIMBSDDProperties
@@ -0,0 +1,132 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import bsdd
import json
import ifcopenshell
import blenderbim.tool as tool
class LoadBSDDDomains(bpy.types.Operator):
bl_idname = "bim.load_bsdd_domains"
bl_label = "Load bSDD Domains"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.domains.clear()
client = bsdd.Client()
for domain in sorted(client.Domain(), key=lambda x: x["name"]):
new = props.domains.add()
new.name = domain["name"]
new.namespace_uri = domain["namespaceUri"]
new.default_language_code = domain["defaultLanguageCode"]
new.organization_name_owner = domain["organizationNameOwner"]
new.status = domain["status"]
new.version = domain["version"]
return {"FINISHED"}
class SetActiveBSDDDomain(bpy.types.Operator):
bl_idname = "bim.set_active_bsdd_domain"
bl_label = "Load bSDD Domains"
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
uri: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.active_domain = self.name
props.active_uri = self.uri
return {"FINISHED"}
class SearchBSDDClassifications(bpy.types.Operator):
bl_idname = "bim.search_bsdd_classifications"
bl_label = "Search bSDD Classifications"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.classifications.clear()
client = bsdd.Client()
related_ifc_entities = []
if len(props.keyword) < 3:
return {"FINISHED"}
if props.should_filter_ifc_class and context.active_object:
element = tool.Ifc.get_entity(context.active_object)
if element:
related_ifc_entities = [element.is_a()]
results = client.ClassificationSearchOpen(props.keyword, DomainNamespaceUris=[props.active_uri], RelatedIfcEntities=related_ifc_entities)
for result in sorted(results["classifications"], key=lambda x: x["referenceCode"]):
new = props.classifications.add()
new.name = result["name"]
new.reference_code = result["referenceCode"]
new.description = result.get("description", "")
new.namespace_uri = result["namespaceUri"]
new.domain_name = result["domainName"]
new.domain_namespace_uri = result["domainNamespaceUri"]
return {"FINISHED"}
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Classifications"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.scene.BIMBSDDProperties
bprops.classification_psets.clear()
bsdd_classification = bprops.classifications[bprops.active_classification_index]
client = bsdd.Client()
data = client.Classification(bsdd_classification.namespace_uri)
properties = data.get("classificationProperties", None)
if not properties:
return {"FINISHED"}
psets = {}
for prop in properties:
if prop.get("propertyDomainName") != "IFC":
continue
pset = prop.get("propertySet", None)
if not pset:
continue
psets.setdefault(pset, {})
predefined_value = prop.get("predefinedValue")
if predefined_value:
possible_values = [predefined_value]
else:
possible_values = prop.get("possibleValues", []) or []
possible_values = [v["value"] for v in possible_values]
psets[pset][prop["name"]] = possible_values
for pset_name, pset in psets.items():
new = bprops.classification_psets.add()
new.name = pset_name
for name, values in pset.items():
new2 = new.properties.add()
new2.name = name
new2.enum_items = json.dumps(values)
new2.data_type = "enum"
return {"FINISHED"}
@@ -0,0 +1,66 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from blenderbim.bim.prop import Attribute, StrProperty
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BSDDDomain(PropertyGroup):
name: StringProperty(name="Name")
namespace_uri: StringProperty(name="URI")
default_language_code: StringProperty(name="Language")
organization_name_owner: StringProperty(name="Organization")
status: StringProperty(name="Status")
version: StringProperty(name="Version")
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
description: StringProperty(name="Description")
namespace_uri: StringProperty(name="Namespace URI")
domain_name: StringProperty(name="Domain Name")
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
class BSDDPset(PropertyGroup):
name: StringProperty(name="Name")
properties: CollectionProperty(name="Properties", type=Attribute)
class BIMBSDDProperties(PropertyGroup):
active_domain: StringProperty(name="Active Domain")
active_uri: StringProperty(name="Active URI")
domains: CollectionProperty(name="Domains", type=BSDDDomain)
active_domain_index: IntProperty(name="Active Domain Index")
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
active_classification_index: IntProperty(name="Active Classification Index")
keyword: StringProperty(name="Keyword")
should_filter_ifc_class: BoolProperty(name="Filter Active IFC Class", default=True)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
@@ -0,0 +1,71 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
class BIM_PT_bsdd(Panel):
bl_label = "buildingSMART Data Dictionary"
bl_idname = "BIM_PT_bsdd"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
def draw(self, context):
props = context.scene.BIMBSDDProperties
if props.active_domain:
row = self.layout.row()
row.label(text="Active: " + props.active_domain, icon="URL")
else:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
if len(props.domains):
self.layout.template_list(
"BIM_UL_bsdd_domains",
"",
props,
"domains",
props,
"active_domain_index",
)
else:
row = self.layout.row()
row.operator("bim.load_bsdd_domains")
class BIM_UL_bsdd_domains(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.organization_name_owner})")
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.namespace_uri
class BIM_UL_bsdd_classifications(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.reference_code)
row.label(text=item.name)
@@ -21,7 +21,9 @@ from . import ui, prop, operator
classes = (
operator.AddClassification,
operator.AddClassificationFromBSDD,
operator.AddClassificationReference,
operator.AddClassificationReferenceFromBSDD,
operator.ChangeClassificationLevel,
operator.DisableEditingClassification,
operator.DisableEditingClassificationReference,
@@ -54,6 +54,25 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
)
class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_from_bsdd"
bl_label = "Add Classification From bSDD"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBSDDProperties
domain = [d for d in props.domains if d.name == props.active_domain][0]
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == props.active_domain or element.Location == domain.namespace_uri:
return
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
)
classification.Source = domain.organization_name_owner
classification.Location = domain.namespace_uri
classification.Edition = domain.version
class EnableEditingClassification(bpy.types.Operator):
bl_idname = "bim.enable_editing_classification"
bl_label = "Enable Editing Classification"
@@ -254,6 +273,70 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
)
class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_reference_from_bsdd"
bl_label = "Add Classification Reference From bSDD"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def _execute(self, context):
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
bprops = context.scene.BIMBSDDProperties
bsdd_classification = bprops.classifications[bprops.active_classification_index]
classification = None
for element in tool.Ifc.get().by_type("IfcClassification"):
if (
element.Name == bsdd_classification.domain_name
or element.Location == bsdd_classification.domain_namespace_uri
):
classification = element
break
if not classification:
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
)
classification.Location = bsdd_classification.domain_namespace_uri
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
product=element,
classification=classification,
identification=bsdd_classification.reference_code,
name=bsdd_classification.name,
)
reference.Location = bsdd_classification.namespace_uri
for classification_pset in bprops.classification_psets:
pset = ifcopenshell.util.element.get_pset(element, classification_pset.name)
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=element, name=classification_pset.name
)
properties = {}
for prop in classification_pset.properties:
properties[prop.name] = prop.get_value()
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=properties)
class ChangeClassificationLevel(bpy.types.Operator):
bl_idname = "bim.change_classification_level"
bl_label = "Change Classification Level"
@@ -48,6 +48,15 @@ class ClassificationReference(PropertyGroup):
class BIMClassificationProperties(PropertyGroup):
classification_source: EnumProperty(
items=[
("FILE", "IFC File", ""),
("BSDD", "buildingSMART Data Dictionary", ""),
("MANUAL", "Manual Entry", ""),
],
name="Classification Source",
default="FILE",
)
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
@@ -48,6 +48,42 @@ class BIM_PT_classifications(Panel):
self.props = context.scene.BIMClassificationProperties
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.props, "classification_source", text="")
if self.props.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.props.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.props.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification)
def draw_add_manual_ui(self, context):
row = self.layout.row()
row.label(text="TODO", icon="ERROR")
def draw_add_bsdd_ui(self, context):
self.bprops = context.scene.BIMBSDDProperties
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
row = self.layout.row()
row.operator("bim.add_classification_from_bsdd", icon="ADD")
def draw_add_file_ui(self, context):
if ClassificationsData.data["has_classification_file"]:
row = self.layout.row(align=True)
row.prop(self.props, "available_classifications", text="")
@@ -58,12 +94,6 @@ class BIM_PT_classifications(Panel):
row.label(text="No Active Classification Library")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification)
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
@@ -84,6 +114,7 @@ class ReferenceUI:
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.bprops = context.scene.BIMBSDDProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
@@ -100,14 +131,76 @@ class ReferenceUI:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.sprops, "classification_source", text="")
if self.sprops.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.sprops.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.sprops.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
def draw_add_manual_ui(self, context):
row = self.layout.row()
row.label(text="TODO", icon="ERROR")
def draw_add_bsdd_ui(self, context):
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
row = self.layout.row(align=True)
row.prop(self.bprops, "keyword", text="")
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
row = self.layout.row()
row.prop(self.bprops, "should_filter_ifc_class")
if len(self.bprops.classifications):
self.layout.template_list(
"BIM_UL_bsdd_classifications",
"",
self.bprops,
"classifications",
self.bprops,
"active_classification_index",
)
else:
row = self.layout.row()
row.label(text="No Search Results")
if self.bprops.active_classification_index < len(self.bprops.classifications):
row = self.layout.row(align=True)
op = row.operator(
"bim.add_classification_reference_from_bsdd", text="Add Classification Reference", icon="ADD"
)
op.obj = self.obj
op.obj_type = self.obj_type
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
if len(self.bprops.classification_psets):
for pset in self.bprops.classification_psets:
box = self.layout.box()
row = box.row()
row.label(text=pset.name, icon="COPY_ID")
blenderbim.bim.helper.draw_attributes(pset.properties, box)
def draw_add_file_ui(self, context):
if not self.data.data["active_classification_library"]:
row = self.layout.row(align=True)
row.label(text="No Active Classification Library")
row.label(text="No Active Classification Library", icon="ERROR")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.label(text=f"Active Classification Library: {self.data.data['active_classification_library']}")
#row.prop(self.sprops, "available_classifications", text="")
# row.prop(self.sprops, "available_classifications", text="")
if not self.sprops.available_library_references:
op = row.operator("bim.change_classification_level", text="", icon="GREASEPENCIL")
op.parent_id = int(self.sprops.available_classifications)
@@ -1,124 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os
import logging
import ifcopenshell
import json
import webbrowser
import tempfile
from blenderbim.bim.ifc import IfcStore
class SelectCobieIfcFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_ifc_file"
bl_label = "Select COBie IFC File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.COBieProperties.cobie_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectCobieJsonFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_json_file"
bl_label = "Select COBie JSON File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.COBieProperties.cobie_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcCobie(bpy.types.Operator):
bl_idname = "bim.execute_ifc_cobie"
bl_label = "Execute IFCCOBie"
file_format: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
props = context.scene.COBieProperties
return props.should_load_from_memory or props.cobie_ifc_file
def execute(self, context):
from cobie import IfcCobieParser
props = context.scene.COBieProperties
if props.should_load_from_memory:
output_dir = tempfile.gettempdir()
else:
output_dir = os.path.dirname(props.cobie_ifc_file)
output = os.path.join(output_dir, "output")
logger = logging.getLogger("IFCtoCOBie")
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s"))
logger = logging.getLogger("IFCtoCOBie")
logger.addHandler(fh)
selector = ifcopenshell.util.selector.Selector()
if props.cobie_json_file:
with open(props.cobie_json_file, "r") as f:
custom_data = json.load(f)
else:
custom_data = {}
parser = IfcCobieParser(logger, selector)
ifc_file = IfcStore.get_file()
if not (ifc_file and props.should_load_from_memory):
ifc_file = props.cobie_ifc_file
parser.parse(
ifc_file,
props.cobie_types,
props.cobie_components,
custom_data,
)
if self.file_format == "xlsx":
from cobie import CobieXlsWriter
writer = CobieXlsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
elif self.file_format == "ods":
from cobie import CobieOdsWriter
writer = CobieOdsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
else:
from cobie import CobieCsvWriter
writer = CobieCsvWriter(parser, output_dir)
writer.write()
webbrowser.open("file://" + output_dir)
webbrowser.open("file://" + output_dir + "/cobie.log")
return {"FINISHED"}
@@ -1,38 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class COBieProperties(PropertyGroup):
cobie_ifc_file: StringProperty(default="", name="COBie IFC File")
cobie_types: StringProperty(default=".COBieType", name="COBie Types")
cobie_components: StringProperty(default=".COBie", name="COBie Components")
cobie_json_file: StringProperty(default="", name="COBie JSON File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -1,63 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_cobie(Panel):
bl_label = "COBie"
bl_idname = "BIM_PT_cobie"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_handover"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.COBieProperties
if IfcStore.get_file():
row = layout.row()
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "cobie_ifc_file")
row.operator("bim.select_cobie_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "cobie_types")
row = layout.row()
row.prop(props, "cobie_components")
row = layout.row(align=True)
row.prop(props, "cobie_json_file")
row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="")
row = layout.row()
op = row.operator("bim.execute_ifc_cobie", text="CSV")
op.file_format = "csv"
op = row.operator("bim.execute_ifc_cobie", text="ODS")
op.file_format = "ods"
op = row.operator("bim.execute_ifc_cobie", text="XLSX")
op.file_format = "xlsx"
@@ -20,18 +20,19 @@ import bpy
from . import ui, prop, operator
classes = (
operator.ConvertToBlender,
operator.CopyDebugInformation,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
operator.InspectFromObject,
operator.InspectFromStepId,
operator.OverrideDisplayType,
operator.ParseExpress,
operator.PrintIfcFile,
operator.PrintObjectPlacement,
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeIfcLinks,
operator.ConvertToBlender,
operator.RewindInspector,
operator.SelectExpressFile,
operator.SelectHighPolygonMeshes,
@@ -443,3 +443,14 @@ class PurgeHdf5Cache(bpy.types.Operator):
def execute(self, context):
core.purge_hdf5_cache(tool.Debug)
return {"FINISHED"}
class OverrideDisplayType(bpy.types.Operator):
bl_idname = "bim.override_display_type"
bl_label = "Override Display Type"
display: bpy.props.StringProperty()
def execute(self, context):
for obj in context.selected_objects:
obj.display_type = self.display
return {"FINISHED"}
@@ -40,3 +40,13 @@ class BIMDebugProperties(PropertyGroup):
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File")
display_type: EnumProperty(
items=[
("BOUNDS", "Bounds", ""),
("WIRE", "Wire", ""),
("SOLID", "Solid", ""),
("TEXTURED", "Textured", ""),
],
name="Display Type",
default="BOUNDS",
)
@@ -83,6 +83,10 @@ class BIM_PT_debug(Panel):
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True)
row.prop(props, "display_type", text="")
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
row = layout.row()
@@ -1926,7 +1926,7 @@ class DecorationsHandler:
if cls.installed:
cls.uninstall()
handler = cls()
# NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR
# NOTE: we USE POST_PIXEL here so that we can use both 3D_POLYLINE_UNIFORM_COLOR
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@@ -1944,6 +1944,7 @@ class DecorationsHandler:
self.decorators[object_type] = self.decorators["FALL"]
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
results = []
for obj in collection.all_objects:
@@ -345,8 +345,12 @@ class SheetBuilder:
def build_drawings(self, root, sheet):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
drawing_id = int(view.attrib["data-id"])
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
try:
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
except:
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
continue
images = view.findall("{http://www.w3.org/2000/svg}image")
@@ -387,8 +391,12 @@ class SheetBuilder:
def build_schedules(self, root, sheet):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
try:
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
except:
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
continue
images = view.findall("{http://www.w3.org/2000/svg}image")
@@ -32,6 +32,7 @@ classes = (
operator.OverrideDuplicateMoveLinkedMacro,
operator.OverrideDuplicateMoveMacro,
operator.OverrideJoin,
operator.OverrideMeshSeparate,
operator.OverrideModeSetEdit,
operator.OverrideModeSetObject,
operator.OverrideOriginSet,
@@ -53,6 +54,7 @@ classes = (
ui.BIM_PT_mesh,
ui.BIM_PT_workarounds,
ui.BIM_MT_object_set_origin,
ui.BIM_MT_separate,
)
@@ -73,6 +75,7 @@ def register():
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
bpy.types.VIEW3D_MT_edit_mesh.append(ui.edit_mesh_menu)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
@@ -116,6 +119,7 @@ def unregister():
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
bpy.types.VIEW3D_MT_edit_mesh.remove(ui.edit_mesh_menu)
del bpy.types.Scene.BIMGeometryProperties
del bpy.types.Object.BIMGeometryProperties
wm = bpy.context.window_manager
@@ -57,6 +57,39 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
class OverrideMeshSeparate(bpy.types.Operator, Operator):
bl_idname = "bim.override_mesh_separate"
bl_label = "IFC Mesh Separate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
type: bpy.props.StringProperty()
def _execute(self, context):
obj = context.active_object
# You cannot separate meshes if the representation is mapped.
relating_type = tool.Root.get_element_type(tool.Ifc.get_entity(obj))
if relating_type and tool.Root.does_type_have_representations(relating_type):
# We toggle edit mode to ensure that once representations are
# unmapped, our Blender mesh only has a single user.
tool.Blender.toggle_edit_mode(context)
bpy.ops.bim.unassign_type(related_object=obj.name)
tool.Blender.toggle_edit_mode(context)
selected_objects = context.selected_objects
bpy.ops.mesh.separate(type=self.type)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
new_objs = [obj]
for new_obj in context.selected_objects:
if new_obj == obj:
continue
# This is not very efficient, it needlessly copies the representations first.
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
new_objs.append(new_obj)
for new_obj in new_objs:
bpy.ops.bim.update_representation(obj=new_obj.name)
class OverrideOriginSet(bpy.types.Operator, Operator):
bl_idname = "bim.override_origin_set"
bl_label = "IFC Origin Set"
@@ -88,8 +121,15 @@ class AddRepresentation(bpy.types.Operator, Operator):
bl_options = {"REGISTER", "UNDO"}
representation_conversion_method: bpy.props.EnumProperty(
items=[
("OUTLINE", "Trace Outline", ""),
("BOX", "Bounding Box", ""),
("OUTLINE", "Trace Outline", "Traces outline by local XY axes, for Profile - by local XZ axes."),
(
"BOX",
"Bounding Box",
"Creates a bounding box representation.\n"
"For Plan context - 2D bounding box by local XY axes,\n"
"for Profile - 2D bounding box by local XZ axes.\n"
"For other contexts - bounding box is 3d.",
),
("PROJECT", "Full Representation", ""),
],
name="Representation Conversion Method",
@@ -499,6 +539,8 @@ class OverrideDelete(bpy.types.Operator):
def _execute(self, context):
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
self.process_arrays(context)
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
@@ -508,6 +550,7 @@ class OverrideDelete(bpy.types.Operator):
tool.Geometry.delete_ifc_object(obj)
else:
bpy.data.objects.remove(obj)
if self.is_batch:
old_file = tool.Ifc.get()
old_file.end_transaction()
@@ -528,6 +571,30 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
def process_arrays(self, context):
selected_objects = set(context.selected_objects)
array_parents = set()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
continue
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
for array_parent in array_parents:
array_parent_obj = tool.Ifc.get_object(array_parent)
data = [(i, data) for i, data in enumerate(tool.Blender.Modifier.Array.get_modifiers_data(array_parent))]
# NOTE: there is a way to remove arrays more precisely but it's more complex
for i, modifier_data in reversed(data):
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
if children.issubset(selected_objects):
with context.temp_override(active_object=array_parent_obj):
bpy.ops.bim.remove_array(item=i)
else:
break # allows to remove only n last layers of an array
class OverrideOutlinerDelete(bpy.types.Operator):
bl_idname = "bim.override_outliner_delete"
@@ -538,7 +605,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
@classmethod
def poll(cls, context):
return len(context.selected_ids) > 0
return len(getattr(context, "selected_ids", [])) > 0
def execute(self, context):
# In this override, we don't check self.hierarchy. This effectively
@@ -653,6 +720,13 @@ class OverrideDuplicateMove(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
@staticmethod
def execute_duplicate_operator(self, context, linked=False):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
@@ -663,7 +737,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
if linked and obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
new_active_obj = new_obj
@@ -675,32 +749,50 @@ class OverrideDuplicateMove(bpy.types.Operator):
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
def _execute(self, context):
@staticmethod
def execute_ifc_duplicate_operator(self, context, linked=False):
objects_to_duplicate = set(context.selected_objects)
# handle arrays
arrays_to_duplicate, array_children = OverrideDuplicateMove.process_arrays(self, context)
objects_to_duplicate -= array_children
for child in array_children:
child.select_set(False)
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
old_to_new = {}
for obj in context.selected_objects:
for obj in objects_to_duplicate:
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
linked_non_ifc_object = linked and not element
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
temp_data = None
if obj.data:
if obj.data and not linked_non_ifc_object:
# assure root.copy_class won't replace the previous mesh globally
temp_data = obj.data.copy()
new_obj.data = temp_data
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if linked_non_ifc_object:
continue
# clear object's collection so it will be able to have it's own
new_obj.BIMObjectProperties.collection = None
# copy the actual class
@@ -711,17 +803,58 @@ class OverrideDuplicateMove(bpy.types.Operator):
tool.Blender.remove_data_block(temp_data)
if new:
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
old_to_new[tool.Ifc.get_entity(obj)] = [new]
# TODO: handle array data for other cases of duplication
array_data = arrays_to_duplicate.get(obj, None)
tool.Model.handle_array_on_copied_element(new, array_data)
if array_data:
for child in tool.Blender.Modifier.Array.get_all_children_objects(new):
child.select_set(True)
# TODO: add new array children to recreate their decomposition too
old_to_new[element] = [new]
if new.is_a("IfcRelSpaceBoundary"):
tool.Boundary.decorate_boundary(new_obj)
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.refresh_ui_data()
@staticmethod
def process_arrays(self, context):
selected_objects = set(context.selected_objects)
array_parents = set()
arrays_to_create = dict()
array_children = set() # will be ignored during the duplication
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
continue
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
for array_parent in array_parents:
array_parent_obj = tool.Ifc.get_object(array_parent)
if array_parent_obj not in selected_objects:
continue
array_data = []
for modifier_data in tool.Blender.Modifier.Array.get_modifiers_data(array_parent):
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
if children.issubset(selected_objects):
modifier_data["children"] = []
array_data.append(modifier_data)
array_children.update(children)
else:
break # allows to duplicate only n first layers of an array
if array_data:
arrays_to_create[array_parent_obj] = array_data
return arrays_to_create, array_children
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
bl_idname = "bim.override_object_duplicate_move_linked_macro"
@@ -739,57 +872,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
return {"FINISHED"}
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj == context.active_object:
new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=True)
def _execute(self, context):
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# Copy the actual class
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
if new:
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
old_to_new[tool.Ifc.get_entity(obj)] = new
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
@@ -809,28 +895,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
return {"FINISHED"}
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
self.new_active_obj = None
@@ -1028,12 +1093,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
tool.Model.handle_array_on_copied_element(new_entity)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
@@ -1068,6 +1128,17 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
recreate_data_structure(new_root_entity)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
@@ -1161,12 +1232,7 @@ class RefreshAggregate(bpy.types.Operator):
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
tool.Model.handle_array_on_copied_element(new_entity)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
@@ -1180,22 +1246,33 @@ class RefreshAggregate(bpy.types.Operator):
return new_entity
if len(context.selected_objects) != 1:
self.report({"INFO"}, "Only 1 object need to be selected.")
return {"FINISHED"}
selected_root_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
if not selected_root_entity.is_a("IfcElementAssembly"):
if selected_root_entity.is_a("IfcElementAssembly"):
pass
elif selected_root_entity.Decomposes:
if selected_root_entity.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_root_entity = selected_root_entity.Decomposes[0].RelatingObject
selected_root_obj = tool.Ifc.get_object(selected_root_entity)
else:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
return {"FINISHED"}
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
original_root_entity = tool.Ifc.get().by_guid(instance_of)
if original_root_entity == selected_root_entity:
self.report({"INFO"}, "Cannot refresh original assembly. Select an assembly instance.")
return {"FINISHED"}
parents = remove_objects(selected_root_entity)
@@ -1208,13 +1285,25 @@ class RefreshAggregate(bpy.types.Operator):
for parent in parents:
duplicate_children(parent)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
new_obj = tool.Ifc.get_object(new[0])
matrix_diff = new_obj.matrix_world @ original_matrix
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
new_matrix = selected_matrix @ matrix_diff
new_obj.matrix_world = new_matrix
@@ -32,6 +32,21 @@ def object_menu(self, context):
self.layout.menu("BIM_MT_object_set_origin", icon="PLUGIN")
def edit_mesh_menu(self, context):
self.layout.separator()
self.layout.menu("BIM_MT_separate", icon="PLUGIN")
class BIM_MT_separate(Menu):
bl_idname = "BIM_MT_separate"
bl_label = "IFC Separate"
def draw(self, context):
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC Selection").type = "SELECTED"
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Material").type = "MATERIAL"
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Loose Parts").type = "LOOSE"
class BIM_MT_object_set_origin(Menu):
bl_idname = "BIM_MT_object_set_origin"
bl_label = "IFC Set Origin"
@@ -22,6 +22,7 @@ import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
@@ -140,7 +141,51 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
core.split_along_edge(tool.Misc, cutter=context.active_object, objs=context.selected_objects)
cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter]
# Splitting only works on meshes
for obj in objs:
# You cannot split meshes if the representation is mapped.
element = tool.Ifc.get_entity(obj)
if element:
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
representation = tool.Geometry.get_active_representation(obj)
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=False,
)
if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs:
bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj)
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
)
class GetConnectedSystemElements(bpy.types.Operator, Operator):
@@ -205,46 +250,50 @@ class DrawSystemArrows(bpy.types.Operator, Operator):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
curve.data.dimensions = "3D"
context.scene.collection.objects.link(curve)
sinks = []
sources = []
for obj in bpy.context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = tool.Ifc.get_entity(obj)
sources = []
sinks = []
for rel in getattr(element, "HasPorts", []) or []:
if rel.RelatingPort.FlowDirection == "SOURCE":
sources.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
elif rel.RelatingPort.FlowDirection == "SINK":
sinks.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
sources_current = []
sinks_current = []
for port in tool.System.get_ports(element):
local_placement = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
m = self.get_absolute_matrix(local_placement)
if port.FlowDirection == "SOURCE":
sources_current.append(m)
elif port.FlowDirection == "SINK":
sinks_current.append(m)
else:
sources.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
sinks.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
for sink in sinks:
for source in sources:
sources_current.append(m)
sinks_current.append(m)
if sinks_current or sources_current:
sinks.append(sinks_current)
sources.append(sources_current)
if not sinks:
self.report({"INFO"}, "No sinks/sources found for selected objects.")
return {"FINISHED"}
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
curve.data.dimensions = "3D"
curve.show_in_front = True
context.scene.collection.objects.link(curve)
for i in range(len(sinks)):
for sink in sinks[i]:
for source in sources[i]:
polyline = curve.data.splines.new("POLY")
polyline.points.add(1)
polyline.points[0].co = (Matrix(sink).translation * unit_scale).to_4d()
polyline.points[1].co = (Matrix(source).translation * unit_scale).to_4d()
tool.Blender.select_and_activate_single_object(context, curve)
def get_absolute_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
@@ -206,15 +206,29 @@ class SelectArrayParent(bpy.types.Operator):
bl_idname = "bim.select_array_parent"
bl_label = "Select Array Parent"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.StringProperty(description="Parent Element GUID")
@classmethod
def poll(cls, context):
if not context.active_object:
cls.poll_message_set("No active object selected")
return False
return True
def execute(self, context):
try:
element = tool.Ifc.get().by_guid(self.parent)
except:
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
object = context.active_object
element = tool.Ifc.get_entity(object)
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not array_pset:
self.report({"ERROR"}, f"Object is not part of an array.")
return {"CANCELLED"}
obj = tool.Ifc.get_object(element)
try:
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
except:
self.report({"ERROR"}, f"Couldn't find array parent by guid '{array_pset['Parent']}'")
return {"CANCELLED"}
obj = tool.Ifc.get_object(parent_element)
if obj:
tool.Blender.select_and_activate_single_object(context, active_object=obj)
return {"FINISHED"}
@@ -224,13 +238,26 @@ class SelectAllArrayObjects(bpy.types.Operator):
bl_idname = "bim.select_all_array_objects"
bl_label = "Select All Array Objects"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.StringProperty(description="Parent Element GUID")
@classmethod
def poll(cls, context):
if not context.active_object:
cls.poll_message_set("No active object selected")
return False
return True
def execute(self, context):
object = context.active_object
element = tool.Ifc.get_entity(object)
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not array_pset:
self.report({"ERROR"}, f"Object is not part of an array.")
return {"CANCELLED"}
try:
parent_element = tool.Ifc.get().by_guid(self.parent)
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
except RuntimeError:
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
self.report({"ERROR"}, f"Couldn't find array parent by guid '{array_pset['Parent']}'")
return {"CANCELLED"}
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
@@ -68,6 +68,14 @@ def update_door_modifier_representation(context, obj):
},
}
def get_active_representation_context(obj):
active_representation = tool.Geometry.get_active_representation(obj)
if active_representation:
return active_representation.ContextOfItems
return ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
previously_active_context = get_active_representation_context(obj)
# ELEVATION_VIEW representation
profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
if profile:
@@ -78,6 +86,7 @@ def update_door_modifier_representation(context, obj):
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
# MODEL_VIEW representation
# (Model/Body defined only BEFORE Plan/Body to prevent #2744)
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
representation_data["context"] = body
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
@@ -113,14 +122,34 @@ def update_door_modifier_representation(context, obj):
)
tool.Model.replace_object_ifc_representation(plan_annotation, obj, plan_representation)
if plan_body or plan_annotation:
# adding switch representation at the end instead of changing order of representations
# to prevent #2744
core.switch_representation(
# adding switch representation at the end instead of changing order of representations
# to prevent #2744
if get_active_representation_context(obj) != previously_active_context:
previously_active_representation = ifcopenshell.util.representation.get_representation(
element,
previously_active_context.ContextType,
previously_active_context.ContextIdentifier,
previously_active_context.TargetView,
)
if not previously_active_representation:
# we assume there is no representation because it was
# Plan/Annotation/PLAN_VIEW
previously_active_context = ifcopenshell.util.representation.get_context(
ifc_file, "Plan", "Body", "PLAN_VIEW"
)
previously_active_representation = ifcopenshell.util.representation.get_representation(
element,
previously_active_context.ContextType,
previously_active_context.ContextIdentifier,
previously_active_context.TargetView,
)
blenderbim.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=model_representation,
representation=previously_active_representation,
should_reload=True,
is_global=True,
should_sync_changes_first=True,
@@ -847,6 +847,7 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
)
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
tool.Model.replace_object_ifc_representation(body, obj, rep)
tool.Blender.remove_data_block(mesh)
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=transition_type, name="BBIM_Fitting")
ifcopenshell.api.run(
"pset.edit_pset",
@@ -1188,6 +1189,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
)
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
tool.Model.replace_object_ifc_representation(body, obj, rep)
tool.Blender.remove_data_block(mesh)
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=bend_type, name="BBIM_Fitting")
ifcopenshell.api.run(
"pset.edit_pset",
@@ -185,8 +185,13 @@ class AddConstrTypeInstance(bpy.types.Operator):
collection_obj = collection.BIMCollectionProperties.obj
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
tool.Blender.remove_data_block(mesh) # Remove "Instance" mesh
mesh_data = obj.data
element = tool.Ifc.get_entity(obj)
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
if obj.data != mesh_data: # remove orphaned mesh from "bim.assign_class"
tool.Blender.remove_data_block(mesh_data)
# Update required as core.type.assign_type may change obj.data
# TODO: This is inefficient. It literally creates a mesh, then potentially removes it.
@@ -132,6 +132,7 @@ class DumbProfileGenerator:
is_global=True,
should_sync_changes_first=False,
)
tool.Blender.remove_data_block(mesh)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
@@ -196,6 +196,7 @@ class DumbSlabGenerator:
is_global=True,
should_sync_changes_first=False,
)
tool.Blender.remove_data_block(mesh)
if self.footprint_context:
extrusion = tool.Model.get_extrusion(representation)
@@ -188,10 +188,8 @@ class BIM_PT_array(bpy.types.Panel):
if ArrayData.data["parameters"]:
row = self.layout.row(align=True)
row.label(text=ArrayData.data["parameters"]["parent_name"], icon="CON_CHILDOF")
op = row.operator("bim.select_array_parent", icon="OBJECT_DATA", text="")
op.parent = ArrayData.data["parameters"]["Parent"]
op = row.operator("bim.select_all_array_objects", icon="RESTRICT_SELECT_OFF", text="")
op.parent = ArrayData.data["parameters"]["Parent"]
row.operator("bim.select_array_parent", icon="OBJECT_DATA", text="")
row.operator("bim.select_all_array_objects", icon="RESTRICT_SELECT_OFF", text="")
if ArrayData.data["parameters"]["data_dict"]:
row.operator("bim.add_array", icon="ADD", text="")
@@ -637,6 +637,7 @@ class DumbWallGenerator:
is_global=True,
should_sync_changes_first=False,
)
tool.Blender.remove_data_block(mesh)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
obj.select_set(True)
@@ -827,10 +828,10 @@ class DumbWallJoiner:
return
for rel in element1.ConnectedTo:
if rel.RelatingConnectionType in ["ATSTART", "ATEND"]:
if rel.is_a("IfcRelConnectsPathElements") and rel.RelatingConnectionType in ["ATSTART", "ATEND"]:
rel.RelatingConnectionType = "ATSTART" if rel.RelatingConnectionType == "ATEND" else "ATEND"
for rel in element1.ConnectedFrom:
if rel.RelatedConnectionType in ["ATSTART", "ATEND"]:
if rel.is_a("IfcRelConnectsPathElements") and rel.RelatedConnectionType in ["ATSTART", "ATEND"]:
rel.RelatedConnectionType = "ATSTART" if rel.RelatedConnectionType == "ATEND" else "ATEND"
layers1 = tool.Model.get_material_layer_parameters(element1)
@@ -81,6 +81,8 @@ def update_simple_openings(element, opening_width, opening_height):
has_replaced_opening_representation = True
tool.Model.reload_body_representation(voided_objs)
with bpy.context.temp_override(selected_objects=[tool.Ifc.get_object(f) for f in fillings]):
bpy.ops.bim.recalculate_fill()
def update_window_modifier_representation(context, obj):
@@ -116,6 +118,14 @@ def update_window_modifier_representation(context, obj):
}
representation_data["panel_properties"].append(panel_data)
def get_active_representation_context(obj):
active_representation = tool.Geometry.get_active_representation(obj)
if active_representation:
return active_representation.ContextOfItems
return ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
previously_active_context = get_active_representation_context(obj)
# ELEVATION_VIEW representation
profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
if profile:
@@ -126,6 +136,7 @@ def update_window_modifier_representation(context, obj):
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
# MODEL_VIEW representation
# (Model/Body defined only BEFORE Plan/Body to prevent #2744)
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
representation_data["context"] = body
model_representation = ifcopenshell.api.run("geometry.add_window_representation", ifc_file, **representation_data)
@@ -140,13 +151,20 @@ def update_window_modifier_representation(context, obj):
)
tool.Model.replace_object_ifc_representation(plan, obj, plan_representation)
# adding switch representation at the end instead of changing order of representations
# to prevent #2744
# adding switch representation at the end instead of changing order of representations
# to prevent #2744
if get_active_representation_context(obj) != previously_active_context:
previously_active_representation = ifcopenshell.util.representation.get_representation(
element,
previously_active_context.ContextType,
previously_active_context.ContextIdentifier,
previously_active_context.TargetView,
)
blenderbim.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=model_representation,
representation=previously_active_representation,
should_reload=True,
is_global=True,
should_sync_changes_first=True,
@@ -347,14 +347,21 @@ class BimToolUI:
"IfcDuctSegment",
"IfcPipeSegment",
):
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
add_layout_hotkey_operator(
cls.layout, "Regen MEP", "S_G", bpy.ops.bim.regenerate_distribution_element.__doc__
)
if context.region.type != "TOOL_HEADER":
cls.layout.operator("bim.mep_add_bend")
cls.layout.operator("bim.mep_add_transition")
cls.layout.operator("bim.mep_add_obstruction")
cls.layout.operator("bim.mep_connect_elements")
else:
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
elif (
@@ -427,6 +434,11 @@ class BimToolUI:
add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Toggle openings")
add_layout_hotkey_operator(cls.layout, "Decomposition", "A_D", "Select decomposition")
cls.layout.separator()
add_layout_hotkey_operator(
cls.layout, "Calculate All Quantities", "S_Q", bpy.ops.bim.calculate_all_quantities.__doc__
)
@classmethod
def draw_header_interface(cls):
cls.draw_type_selection_interface()
@@ -667,7 +679,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if self.active_material_usage == "LAYER2":
bpy.ops.bim.recalculate_wall()
elif self.active_material_usage == "PROFILE":
bpy.ops.bim.recalculate_profile()
if self.active_class in (
"IfcCableCarrierSegment",
"IfcCableSegment",
"IfcDuctSegment",
"IfcPipeSegment",
):
bpy.ops.bim.regenerate_distribution_element()
else:
bpy.ops.bim.recalculate_profile()
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
bpy.ops.bim.recalculate_fill()
elif self.active_class in ("IfcSpace"):
@@ -71,10 +71,10 @@ mapper = {
'Area' : "get_net_side_area",
},
'Qto_DuctSegmentBaseQuantities' : {
'Length' : None,
'Length' : "get_length",
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'OuterSurfaceArea' : "get_outer_surface_area",
'GrossWeight' : None,
},
'Qto_TransformerBaseQuantities' : {
@@ -203,10 +203,10 @@ mapper = {
'Weight' : None,
},
'Qto_DuctFittingBaseQuantities' : {
'Length' : None,
'Length' : "get_length",
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'OuterSurfaceArea' : "get_outer_surface_area",
'GrossWeight' : None,
},
'Qto_UnitaryControlElementBaseQuantities' : {
@@ -31,6 +31,9 @@ NetFloorArea: it doesn't count the following entities contained in the spatial e
NetVolume: like NetFloorArea, it doesn't count the following entities contained in the spatial entity: IfcColumn and IfcWall
Also, the entire IfcColumn (or IfcWall) object volume is substracted, so it should be better to substract only the shared volume between IfcSpace and IfcColumn. Look at todo list
DUCT SEGMENTS AND DUCT FITTINGS
The length is calculated like a beam. Also outer surface area. Gross cross section area, net cross section area and weight are not calculated right now because the parametrically cross section area seems filled (without hole).
WEIGHT
The object weight is calculated by multiplying the object mass density with the object volume (net or gross).
It's only calculated if the object material has a MassDensity property in the Pset_MaterialCommon.
@@ -223,7 +223,7 @@ class EnablePsetEditing(bpy.types.Operator):
new.is_selected = enum in selected_enum_items
else:
if prop.is_a("IfcPropertySingleValue"):
value = prop.NominalValue.wrappedValue
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
elif prop.is_a("IfcPhysicalSimpleQuantity"):
value = prop[3]
new_prop = self.props.properties.add()
@@ -356,24 +356,7 @@ class AddPset(bpy.types.Operator, Operator):
obj_type: bpy.props.StringProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
pset_name = get_pset_props(context, self.obj, self.obj_type).pset_name
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in tool.Blender.get_selected_objects()]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
if pset_name in blenderbim.bim.schema.ifc.psetqto.get_applicable_names(element.is_a(), pset_only=True):
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=pset_name, pset_type="PSET", obj=obj, obj_type=self.obj_type
)
core.add_pset(tool.Ifc, tool.Pset, tool.Blender, obj_name=self.obj, obj_type=self.obj_type)
class AddQto(bpy.types.Operator, Operator):
@@ -49,7 +49,6 @@ classes = (
operator.ExpandResource,
operator.GoToResource,
operator.ImportResources,
operator.LoadResourceProperties,
operator.LoadResources,
operator.RemoveResource,
operator.RemoveResourceQuantity,
@@ -33,19 +33,6 @@ class LoadResources(bpy.types.Operator):
return {"FINISHED"}
class LoadResourceProperties(bpy.types.Operator):
bl_idname = "bim.load_resource_properties"
bl_label = "Load Resource Properties"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
core.load_resource_properties(
tool.Resource, resource=tool.Ifc.get().by_id(self.resource) if self.resource else None
)
return {"FINISHED"}
class AddResource(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_resource"
bl_label = "Add Resource"
@@ -437,4 +424,4 @@ class CalculateResourceUsage(bpy.types.Operator, tool.Ifc.Operator):
return False
def _execute(self, context):
core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(tool.Resource.get_highlighted_resource()))
core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Resource.get_highlighted_resource())
@@ -81,8 +81,7 @@ def update_active_resource_index(self, context):
def updateResourceUsage(self, context):
props = context.scene.BIMResourceProperties
if not props.is_resource_update_enabled:
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
return
if not self.schedule_usage:
return
@@ -20,7 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.ActivateIfcBuildingStoreyFilter,
operator.ActivateContainerFilter,
operator.ActivateIfcClassFilter,
operator.AddFilter,
operator.AddFilterGroup,
@@ -58,6 +58,7 @@ classes = (
prop.SearchQueryGroup,
prop.IfcSelectorProperties,
ui.BIM_PT_search,
ui.BIM_PT_filter,
ui.BIM_PT_colour_by_property,
ui.BIM_PT_select_similar,
ui.BIM_UL_colourscheme,
@@ -466,8 +466,8 @@ class ToggleFilterSelection(Operator):
if props.filter_type == "CLASSES":
for ifc_class in props.filter_classes:
ifc_class.is_selected = self.selecting_actionbool
elif props.filter_type == "BUILDINGSTOREYS":
for building_storey in props.filter_building_storeys:
elif props.filter_type == "CONTAINER":
for building_storey in props.filter_container:
building_storey.is_selected = self.selecting_actionbool
return {"FINISHED"}
@@ -525,11 +525,11 @@ class ActivateIfcClassFilter(Operator):
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
class ActivateIfcBuildingStoreyFilter(Operator):
class ActivateContainerFilter(Operator):
"""Filter the current selection by Building Storey"""
bl_idname = "bim.activate_ifc_building_storey_filter"
bl_label = "Filter by Building Storey"
bl_idname = "bim.activate_ifc_container_filter"
bl_label = "Filter by Container"
@classmethod
def poll(cls, context):
@@ -540,27 +540,29 @@ class ActivateIfcBuildingStoreyFilter(Operator):
def invoke(self, context, event):
props = bpy.context.scene.BIMSearchProperties
props.filter_building_storeys.clear()
props.filter_container.clear()
ifc_building_storeys = {}
containers = {}
containers.setdefault("None", 0)
for obj in context.selected_objects:
storey = tool.Misc.get_object_storey(obj)
if not storey:
container = tool.Spatial.get_container(tool.Ifc.get_entity(obj))
if not container:
containers["None"] += 1
continue
ifc_building_storeys.setdefault(storey.Name, 0)
ifc_building_storeys[storey.Name] += 1
containers.setdefault(container.Name, 0)
containers[container.Name] += 1
for name, total in dict(sorted(ifc_building_storeys.items())).items():
new = props.filter_building_storeys.add()
for name, total in dict(sorted(containers.items())).items():
new = props.filter_container.add()
new.name = name
new.total = total
props.filter_type = "BUILDINGSTOREYS"
props.filter_type = "CONTAINER"
return context.window_manager.invoke_props_dialog(self, width=250)
def execute(self, context):
bpy.context.scene.BIMSearchProperties.filter_building_storeys.clear()
bpy.context.scene.BIMSearchProperties.filter_container.clear()
return {"FINISHED"}
def draw(self, context):
@@ -568,12 +570,12 @@ class ActivateIfcBuildingStoreyFilter(Operator):
"BIM_UL_ifc_building_storey_filter",
"",
context.scene.BIMSearchProperties,
"filter_building_storeys",
"filter_container",
context.scene.BIMSearchProperties,
"filter_building_storeys_index",
"filter_container_index",
rows=20
if len(bpy.context.scene.BIMSearchProperties.filter_building_storeys) > 20
else len(bpy.context.scene.BIMSearchProperties.filter_building_storeys),
if len(bpy.context.scene.BIMSearchProperties.filter_container) > 20
else len(bpy.context.scene.BIMSearchProperties.filter_container),
)
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
@@ -70,15 +70,15 @@ def update_is_class_selected(self, context):
new.obj = obj
def update_is_level_selected(self, context):
def update_is_container_selected(self, context):
if self.is_selected:
for obj in self.unselected_objects:
obj.obj.select_set(True)
self.unselected_objects.clear()
else:
for obj in context.selected_objects:
level = tool.Misc.get_object_storey(obj)
if level and level.Name == self.name:
container = tool.Spatial.get_container(tool.Ifc.get_entity(obj))
if (container and container.Name == self.name) or (not container and self.name== "None"):
obj.select_set(False)
new = self.unselected_objects.add()
new.obj = obj
@@ -93,7 +93,7 @@ class BIMFilterClasses(PropertyGroup):
class BIMFilterBuildingStoreys(PropertyGroup):
name: StringProperty(name="Name")
is_selected: BoolProperty(name="Is Level Selected", default=True, update=update_is_level_selected)
is_selected: BoolProperty(name="Is Level Selected", default=True, update=update_is_container_selected)
total: IntProperty(name="Total")
unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects")
@@ -140,8 +140,8 @@ class BIMSearchProperties(PropertyGroup):
filter_type: StringProperty(name="Filter Type")
filter_classes: CollectionProperty(type=BIMFilterClasses, name="Filter Classes")
filter_classes_index: IntProperty(name="Filter Classes Index")
filter_building_storeys: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level")
filter_building_storeys_index: IntProperty(name="Filter Level Index")
filter_container: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level")
filter_container_index: IntProperty(name="Filter Level Index")
def get_classes(self, ifc_product):
@@ -42,11 +42,21 @@ class BIM_PT_search(Panel):
row = self.layout.row(align=True)
row.operator("bim.search", text="Search", icon="VIEWZOOM")
return # Temporary for now whilst searching is being upgraded.
return
class BIM_PT_filter(Panel):
bl_label = "Filter Selection"
bl_idname = "BIM_PT_filter"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_grouping_and_filtering"
def draw(self, context):
row = self.layout.row(align=True)
row.operator("bim.activate_ifc_class_filter", icon="FILTER")
row.operator("bim.activate_ifc_building_storey_filter", icon="FILTER")
row.operator("bim.activate_ifc_container_filter", icon="FILTER")
class BIM_PT_colour_by_property(Panel):
@@ -372,6 +372,7 @@ class EditTaskTime(bpy.types.Operator, tool.Ifc.Operator):
core.edit_task_time(
tool.Ifc,
tool.Sequence,
tool.Resource,
task_time=tool.Ifc.get().by_id(context.scene.BIMWorkScheduleProperties.active_task_time_id),
)
@@ -508,7 +509,7 @@ class AssignProcess(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
if self.related_object_type == "RESOURCE":
core.assign_resource(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task))
core.assign_resource(tool.Ifc, tool.Sequence, tool.Resource, task=tool.Ifc.get().by_id(self.task))
elif self.related_object_type == "PRODUCT":
if self.related_object:
core.assign_input_products(
@@ -541,6 +542,7 @@ class UnassignProcess(bpy.types.Operator):
core.unassign_resource(
tool.Ifc,
tool.Sequence,
tool.Resource,
task=tool.Ifc.get().by_id(self.task),
resource=tool.Ifc.get().by_id(self.resource),
)
@@ -224,10 +224,8 @@ def updateTaskDuration(self, context):
else:
task_time = tool.Ifc.run("sequence.add_task_time", task=task)
tool.Ifc.run("sequence.edit_task_time", task_time=task_time, attributes={"ScheduleDuration": duration})
SequenceData.load()
blenderbim.core.sequence.load_task_properties(tool.Sequence)
bpy.ops.bim.load_task_properties()
tool.Sequence.load_resources()
tool.Sequence.refresh_task_resources()
def get_schedule_predefined_types(self, context):
@@ -331,22 +329,24 @@ def get_saved_color_schemes(self, context):
def updateAssignedResourceName(self, context):
pass
def updateAssignedResourceUsage(self, context):
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
return
if not self.schedule_usage:
return
resource = tool.Ifc.get().by_id(self.ifc_definition_id)
if resource.Usage and resource.Usage.ScheduleUsage == self.schedule_usage:
return
tool.Resource.run_edit_resource_time(resource, attributes={
"ScheduleUsage": self.schedule_usage
})
tool.Resource.run_edit_resource_time(resource, attributes={"ScheduleUsage": self.schedule_usage})
tool.Sequence.load_task_properties()
tool.Resource.load_resource_properties()
tool.Sequence.refresh_task_resources()
blenderbim.bim.module.resource.data.refresh()
blenderbim.bim.module.sequence.data.refresh()
refresh_sequence_data()
blenderbim.bim.module.pset.data.refresh()
class Task(PropertyGroup):
name: StringProperty(name="Name", update=updateTaskName)
identification: StringProperty(name="Identification", update=updateTaskIdentification)
@@ -727,7 +727,11 @@ class BIM_PT_task_icom(Panel):
if total_task_outputs:
op = row2.operator("bim.unassign_product", icon="REMOVE", text="")
op.task = task.ifc_definition_id
if not context.selected_objects and self.props.active_task_output_index < total_task_outputs:
if (
total_task_outputs
and not context.selected_objects
and self.props.active_task_output_index < total_task_outputs
):
output_id = self.props.task_outputs[self.props.active_task_output_index].ifc_definition_id
op.relating_product = output_id
@@ -770,6 +774,7 @@ class BIM_UL_task_resources(UIList):
row.prop(item, "name", emboss=False, text="")
row.prop(item, "schedule_usage", emboss=False, text="")
class BIM_UL_animation_colors(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, prop, operator
from . import ui, prop, operator, decorator
classes = (
operator.AddPort,
@@ -51,7 +51,9 @@ classes = (
def register():
bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
def unregister():
del bpy.types.Scene.BIMSystemProperties
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
@@ -27,6 +27,7 @@ def refresh():
SystemData.is_loaded = False
ObjectSystemData.is_loaded = False
PortData.is_loaded = False
SystemDecorationData.is_loaded = False
class SystemData:
@@ -140,3 +141,19 @@ class PortData:
data.append((port, port_obj, connected_element))
return data
class SystemDecorationData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"decoration_data": cls.decoration_data(),
}
cls.is_loaded = True
@classmethod
def decoration_data(cls):
return tool.System.get_decoration_data()
@@ -0,0 +1,145 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import gpu
import bmesh
import blenderbim.tool as tool
from math import sin, cos, radians
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
from gpu_extras.batch import batch_for_shader
import ifcopenshell
from blenderbim.bim.module.system.data import SystemDecorationData
from bpy.app.handlers import persistent
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
def transparent_color(color, alpha=0.1):
color = [i for i in color]
color[3] = alpha
return color
@persistent
def toggle_decorations_on_load(*args):
if bpy.context.scene.BIMSystemProperties.should_draw_decorations:
SystemDecorator.install(bpy.context)
else:
SystemDecorator.uninstall()
class SystemDecorator:
installed = None
@classmethod
def install(cls, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
"""Note that operators that change mesh in `exit_edit_mode_callback` can freeze blender.
The workaround is to move their code to function and use it for callback.
Example: https://devtalk.blender.org/t/calling-operator-that-saves-bmesh-freezes-blender-forever/28595"""
if cls.installed:
cls.uninstall()
handler = cls()
cls.installed = SpaceView3D.draw_handler_add(
handler, (context, get_custom_bmesh, draw_faces, exit_edit_mode_callback), "WINDOW", "POST_VIEW"
)
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
except ValueError:
pass
cls.installed = None
def draw_batch(self, shader_type, content_pos, color, indices=None):
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def draw_faces(self, bm, vertices_coords):
"""mutates original bm (triangulates it)
so the triangulation edges will be shown too
"""
traingulated_bm = bm
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
### Actually drawing
all_vertices = []
error_vertices = []
selected_vertices = []
unselected_vertices = []
# special = associated with arcs/circles
special_vertices = []
special_vertex_indices = {}
selected_edges = []
unselected_edges = []
arc_edges = []
roof_angle_edges = []
preview_edges = []
if not SystemDecorationData.is_loaded:
SystemDecorationData.load()
decoration_data = SystemDecorationData.data["decoration_data"]
all_vertices = decoration_data["all_vertices"]
preview_edges = decoration_data["preview_edges"]
special_vertices = decoration_data["special_vertices"]
selected_edges = decoration_data["selected_edges"]
selected_vertices = decoration_data["selected_vertices"]
### Actually drawing
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
self.line_shader.uniform_float("lineWidth", 2.0)
# general shader
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
self.shader.bind()
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
self.draw_batch("LINES", all_vertices, UNSPECIAL_ELEMENT_COLOR, arc_edges)
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
self.draw_batch("POINTS", special_vertices, special_elements_color)
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
@@ -219,12 +219,15 @@ class DisconnectPort(bpy.types.Operator, Operator):
class MEPConnectElements(bpy.types.Operator, Operator):
bl_idname = "bim.mep_connect_elements"
bl_label = "Connect MEP Elements"
bl_description = "Connects two selected elements if they have ports with matching location"
bl_description = "Connects two selected elements by their closest located ports and adjusts them"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return len(context.selected_objects) == 2
if not len(context.selected_objects) == 2:
cls.poll_message_set("Need to select 2 objects.")
return False
return True
def _execute(self, context):
obj1 = context.active_object
@@ -233,23 +236,32 @@ class MEPConnectElements(bpy.types.Operator, Operator):
el1 = tool.Ifc.get_entity(obj1)
el2 = tool.Ifc.get_entity(obj2)
connected_elements = ifcopenshell.util.system.get_connected_to(el1)
connected_elements += ifcopenshell.util.system.get_connected_to(el2)
if el2 in connected_elements:
self.report({"ERROR"}, "MEP elements are already connected to each other.")
return {"CANCELLED"}
obj1_ports = [p for p in tool.System.get_ports(el1) if not tool.System.get_connected_port(p)]
obj2_ports = [p for p in tool.System.get_ports(el2) if not tool.System.get_connected_port(p)]
if not obj1_ports or not obj2_ports:
self.report({"ERROR"}, "Couldn't find free ports to connect.")
return
return {"CANCELLED"}
ports_distance = dict()
for port1 in obj1_ports:
port1_location = tool.Model.get_element_matrix(port1).translation
for port2 in obj2_ports:
port2_location = tool.Model.get_element_matrix(port2).translation
if tool.Cad.are_vectors_equal(port1_location, port2_location):
core.connect_port(tool.Ifc, port1, port2)
return {"FINISHED"}
distance = (port1_location - port2_location).length
ports_distance[(port1, port2)] = distance
self.report({"ERROR"}, "Couldn't find any matching ports to connect.")
return {"CANCELLED"}
closest_ports = min(ports_distance, key=lambda x: ports_distance[x])
core.connect_port(tool.Ifc, *closest_ports)
bpy.ops.bim.regenerate_distribution_element()
return {"FINISHED"}
class SetFlowDirection(bpy.types.Operator, Operator):
@@ -18,6 +18,7 @@
import bpy
from blenderbim.bim.module.system.data import SystemData
import blenderbim.bim.module.system.decorator as decorator
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -44,6 +45,14 @@ class System(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
def toggle_decorations(self, context):
toggle = self.should_draw_decorations
if toggle:
decorator.SystemDecorator.install(context)
else:
decorator.SystemDecorator.uninstall()
class BIMSystemProperties(PropertyGroup):
system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -52,3 +61,6 @@ class BIMSystemProperties(PropertyGroup):
active_system_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id")
system_class: EnumProperty(items=get_system_class, name="Class")
should_draw_decorations: BoolProperty(
name="Should Draw Decorations", description="Toggle system decorations", update=toggle_decorations
)
@@ -101,6 +101,10 @@ class BIM_PT_object_systems(Panel):
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.prop(self.props, "should_draw_decorations")
if self.props.is_editing:
row = self.layout.row()
row.alignment = "RIGHT"
@@ -131,7 +131,8 @@ class SelectRequirement(bpy.types.Operator):
props.failed_entities.clear()
for e in failed_entities:
new_entity = props.failed_entities.add()
new_entity.element = e["element"]
new_entity.ifc_id = e["id"]
new_entity.element = f'{e["class"]}/{e["name"]}'
new_entity.reason = e["reason"]
return {"FINISHED"}
@@ -41,8 +41,9 @@ class Specification(PropertyGroup):
class FailedEntities(PropertyGroup):
reason: StringProperty(name="Reason")
ifc_id: IntProperty(name="IFC ID")
element: StringProperty(name="Element")
reason: StringProperty(name="Reason")
class IfcTesterProperties(PropertyGroup):
@@ -115,17 +115,9 @@ class BIM_UL_tester_failed_entities(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.IfcTesterProperties
if item:
if props.should_load_from_memory:
ifc_file = tool.Ifc.get()
ifc_id = int(item.element[1 : item.element.find("=")])
entity = ifc_file.by_id(ifc_id)
report_entity = f"[#{ifc_id}][{entity.is_a()}] {entity.Name}"
else:
report_entity = item.element
row = layout.row(align=True)
row.label(text=report_entity)
row.label(text=item.element)
row.label(text=item.reason)
if props.should_load_from_memory:
op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF")
op.ifc_id = entity.id()
op.ifc_id = item.ifc_id
+49 -6
View File
@@ -42,7 +42,7 @@ from math import radians
class SetTab(bpy.types.Operator):
bl_idname = "bim.set_tab"
# NOTE: bl_label is set to empty string intentionally
# NOTE: bl_label is set to empty string intentionally
# to avoid showing the operator's name in the tooltips, see #3704
bl_label = ""
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
@@ -197,21 +197,52 @@ class FileAssociate(bpy.types.Operator):
@classmethod
def poll(cls, context):
if platform.system() == "Linux":
if platform.system() in ("Linux", "Windows"):
return True
cls.poll_message_set("Option available only on Linux.")
# TODO Windows and Darwin
cls.poll_message_set("Option available only on Windows & Linux.")
# TODO Darwin
# https://stackoverflow.com/questions/1082889/how-to-change-filetype-association-in-the-registry
return False
def draw(self, context):
# NOTE: really weird thing on windows that typing this command in cmd works
# when even if you create .bat with the command below and run it as administrator it won't
# Haven't found a workaround yet to automate process completely.
command = "ASSOC .IFC=BLENDERBIM"
self.layout.label(text="On the next step to create file association ")
self.layout.label(text="the system console will be opened ")
self.layout.label(text=f"and you will be asked to type command")
self.layout.label(text=f'"{command}"')
self.layout.label(text="to create an association.")
def invoke(self, context, event):
if platform.system() == "Windows":
return context.window_manager.invoke_props_dialog(self)
else:
return self.execute(context)
def execute(self, context):
src_dir = os.path.join(os.path.dirname(__file__), "../libs/desktop")
binary_path = bpy.app.binary_path
if platform.system() == "Linux":
destdir = os.path.join(os.environ["HOME"], ".local")
self.install_desktop_linux(src_dir=src_dir, destdir=destdir, binary_path=binary_path)
elif platform.system() == "Windows":
self.install_desktop_windows(src_dir, binary_path)
self.report({"INFO"}, "Associations established.")
return {"FINISHED"}
def install_desktop_windows(self, src_dir, binary_path):
# very important to clear this regitstry key before creating new association
# tried to do the regitsry change from powershell/cmd - but even admin rights are not enough
# this is why we're using .reg
reg_change_path = os.path.join(src_dir, "windows_bbim_association.reg")
subprocess.run(["cmd", "/c", reg_change_path])
ps_script_path = os.path.join(src_dir, "windows_bbim_association.ps1")
# NOTE: call powershell with RunAs to get admin rights from user
subprocess.run(["powershell", "-file", ps_script_path, binary_path], shell=True)
def install_desktop_linux(self, src_dir=None, destdir="/tmp", binary_path="/usr/bin/blender"):
"""Creates linux file assocations and launcher icon"""
@@ -272,17 +303,29 @@ class FileUnassociate(bpy.types.Operator):
@classmethod
def poll(cls, context):
if platform.system() == "Linux":
if platform.system() in ("Linux", "Windows"):
return True
cls.poll_message_set("Option available only on Linux.")
cls.poll_message_set("Option available only on Windows & Linux.")
return False
def execute(self, context):
if platform.system() == "Linux":
destdir = os.path.join(os.environ["HOME"], ".local")
self.uninstall_desktop_linux(destdir=destdir)
elif platform.system() == "Windows":
self.uninstall_desktop_windows()
return {"FINISHED"}
def uninstall_desktop_windows(self):
# NOTE: call powershell with RunAs to get admin rights from user
cmd = [
"powershell",
"-Command",
"Start-Process -Verb RunAs -Wait cmd -ArgumentList '/c reg delete HKCR\\BLENDERBIM /f'",
]
subprocess.run(cmd, check=True)
self.report({"INFO"}, "Association removed.")
def uninstall_desktop_linux(self, destdir="/tmp"):
"""Removes linux file assocations and launcher icon"""
for rel_path in (
+27 -6
View File
@@ -1,4 +1,4 @@
def add_cost_schedule(ifc, name, predefined_type,object_type):
def add_cost_schedule(ifc, name, predefined_type, object_type):
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type, object_type=object_type)
@@ -112,70 +112,89 @@ def assign_cost_item_quantity(ifc, cost, cost_item, related_object_type, prop_na
ifc.run("cost.assign_cost_item_quantity", cost_item=cost_item, products=products, prop_name=prop_name)
cost.load_cost_item_quantity_assignments(cost_item, related_object_type=related_object_type)
def load_cost_item_quantities(cost):
cost.load_cost_item_quantities()
def load_cost_item_element_quantities(cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PRODUCT")
def load_cost_item_task_quantities(cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PROCESS")
def load_cost_item_resource_quantities(cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="RESOURCE")
def assign_cost_value(ifc, cost_item, cost_rate):
ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
def load_schedule_of_rates(cost, schedule_of_rates):
cost.load_schedule_of_rates_tree(schedule_of_rates)
def unassign_cost_item_quantity(ifc, cost, cost_item, products):
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
cost.load_cost_item_quantities()
def enable_editing_cost_item_quantities(cost, cost_item):
cost.enable_editing_cost_item_quantities(cost_item)
def enable_editing_cost_item_values(cost, cost_item):
cost.enable_editing_cost_item_values(cost_item)
def add_cost_item_quantity(ifc, cost_item, ifc_class):
ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=ifc_class)
def remove_cost_item_quantity(ifc, cost_item, physical_quantity):
ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity)
def enable_editing_cost_item_quantity(cost, physical_quantity):
cost.load_cost_item_quantity_attributes(physical_quantity)
cost.enable_editing_cost_item_quantity(physical_quantity)
def disable_editing_cost_item_quantity(cost):
cost.disable_editing_cost_item_quantity()
def edit_cost_item_quantity(ifc, cost, physical_quantity):
attributes = cost.get_cost_item_quantity_attributes()
ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
cost.disable_editing_cost_item_quantity()
cost.load_cost_item_quantities()
def add_cost_value(ifc, cost, parent, cost_type, cost_category):
value = ifc.run("cost.add_cost_value", parent=parent)
ifc.run(
"cost.edit_cost_value",
cost_value=value,
attributes=cost.get_attributes_for_cost_value(cost_type, cost_category))
attributes=cost.get_attributes_for_cost_value(cost_type, cost_category),
)
def remove_cost_value(ifc, parent, cost_value):
ifc.run("cost.remove_cost_value", parent=parent, cost_value=cost_value)
def enable_editing_cost_item_value(cost, cost_value):
cost.load_cost_item_value_attributes(cost_value)
cost.enable_editing_cost_item_value(cost_value)
def disable_editing_cost_item_value(cost):
cost.disable_editing_cost_item_value()
@@ -195,7 +214,7 @@ def edit_cost_value(ifc, cost, cost_value):
attributes = cost.get_cost_value_attributes()
ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes)
cost.disable_editing_cost_item_value()
#cost.load_cost_item_values(cost.get_highlighted_cost_item())
# cost.load_cost_item_values(cost.get_highlighted_cost_item())
def copy_cost_item_values(ifc, cost, source, destination):
@@ -275,11 +294,12 @@ def change_parent_cost_item(ifc, cost, new_parent):
cost_item = cost.get_active_cost_item()
if cost_item and cost.is_root_cost_item(cost_item):
return "Cannot change root cost item"
if cost_item :
if cost_item:
ifc.run("nest.change_nest", item=cost_item, new_parent=new_parent)
cost.disable_editing_cost_item_parent()
cost.load_cost_schedule_tree()
def copy_cost_item(ifc, cost):
cost_item = cost.get_highlighted_cost_item()
if cost_item:
@@ -287,9 +307,10 @@ def copy_cost_item(ifc, cost):
cost.disable_editing_cost_item_parent()
cost.load_cost_schedule_tree()
def add_currency(ifc, cost):
unit = ifc.run("unit.add_monetary_unit")
attributes = cost.get_currency_attributes()
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
ifc.run("unit.assign_unit", units=[unit])
return unit
return unit
-8
View File
@@ -28,11 +28,3 @@ def resize_to_storey(misc, obj=None, total_storeys=None):
misc.move_object_to_elevation(obj, misc.get_storey_elevation_in_si(storey))
misc.scale_object_to_height(obj, height)
misc.mark_object_as_edited(obj)
def split_along_edge(misc, cutter=None, objs=None):
new_objs = misc.split_objects_with_cutter(objs, cutter)
for obj in new_objs:
misc.run_root_copy_class(obj=obj)
for obj in objs:
misc.mark_object_as_edited(obj)
+1
View File
@@ -43,3 +43,4 @@ def add_pset(ifc, pset, blender, obj_name, obj_type):
ifc_pset = pset.get_element_pset(element, pset_name)
if not ifc_pset:
ifc.run("pset.add_pset", product=element, name=pset_name)
pset.enable_pset_editing(pset_id=0, pset_name=pset_name, pset_type="PSET",obj=obj_name, obj_type=obj_type)
+10 -12
View File
@@ -21,15 +21,11 @@
def load_resources(resource):
resource.load_resources()
resource.load_resource_properties()
def add_resource(tool_ifc, resource_tool, ifc_class, parent_resource=None):
tool_ifc.run("resource.add_resource", ifc_class=ifc_class, parent_resource=parent_resource)
load_resources(resource_tool)
def load_resource_properties(resource_tool, resource=None):
resource_tool.load_resource_properties()
resource_tool.load_resources()
def disable_editing_resource(resource_tool):
@@ -54,7 +50,7 @@ def edit_resource(ifc, resource_tool, resource):
def remove_resource(ifc, resource_tool, resource=None):
ifc.run("resource.remove_resource", resource=resource)
load_resources(resource_tool)
resource_tool.load_resources()
def enable_editing_resource_time(ifc_tool, resource_tool, resource):
@@ -82,7 +78,7 @@ def calculate_resource_work(ifc, resource_tool, resource):
nested_resources = resource_tool.get_nested_resources(resource)
for nested_resource in nested_resources or []:
ifc.run("resource.calculate_resource_work", resource=nested_resource)
load_resources(resource_tool)
resource_tool.load_resources()
def enable_editing_resource_costs(resource_tool, resource):
@@ -143,17 +139,17 @@ def edit_resource_quantity(resource_tool, ifc, physical_quantity=None):
def import_resources(resource_tool, file_path):
resource_tool.import_resources(file_path)
load_resources(resource_tool)
resource_tool.load_resources()
def expand_resource(resource_tool, resource):
resource_tool.expand_resource(resource)
load_resources(resource_tool)
resource_tool.load_resources()
def contract_resource(resource_tool, resource):
resource_tool.contract_resource(resource)
load_resources(resource_tool)
resource_tool.load_resources()
def assign_resource(ifc, spatial, resource=None, products=None):
@@ -213,9 +209,11 @@ def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
ifc.run("constraint.unassign_constraint", product=resource, constraint=constraint)
ifc.run("constraint.remove_constraint", constraint=constraint)
def go_to_resource(resource_tool, resource):
resource_tool.go_to_resource(resource)
def calculate_resource_usage(ifc, resource_tool, resource):
ifc.run("resource.calculate_resource_usage", resource=resource)
load_resources(resource_tool)
resource_tool.load_resources()
+9 -11
View File
@@ -186,14 +186,14 @@ def enable_editing_task_time(ifc, sequence, task=None):
sequence.enable_editing_task_time(task)
def edit_task_time(ifc, sequence, task_time=None):
def edit_task_time(ifc, sequence, resource, task_time=None):
attributes = sequence.get_task_time_attributes()
# TODO: nasty loop goes on when calendar props are messed up
ifc.run("sequence.edit_task_time", task_time=task_time, attributes=attributes)
task = sequence.get_active_task()
sequence.load_task_properties(task=task)
sequence.disable_editing_task_time()
sequence.load_resources()
resource.load_resource_properties()
def assign_predecessor(ifc, sequence, task=None):
@@ -248,8 +248,8 @@ def unassign_input_products(ifc, sequence, spatial, task=None, products=None):
sequence.load_task_inputs(inputs)
def assign_resource(ifc, sequence, task=None):
resource = sequence.get_selected_resource()
def assign_resource(ifc, sequence, resource_tool, task=None):
resource = resource_tool.get_highlighted_resource()
sub_resource = ifc.run(
"resource.add_resource",
parent_resource=resource,
@@ -257,17 +257,15 @@ def assign_resource(ifc, sequence, task=None):
name="{}/{}".format(resource.Name or "Unnamed", task.Name or ""),
)
ifc.run("sequence.assign_process", relating_process=task, related_object=sub_resource)
resources = sequence.get_task_resources(task)
sequence.load_task_resources(resources)
sequence.load_resources()
sequence.load_task_resources(task)
resource_tool.load_resources()
def unassign_resource(ifc, sequence, task=None, resource=None):
def unassign_resource(ifc, sequence, resource_tool, task=None, resource=None):
ifc.run("sequence.unassign_process", relating_process=task, related_object=resource)
ifc.run("resource.remove_resource", resource=resource)
resources = sequence.get_task_resources(task)
sequence.load_task_resources(resources)
sequence.load_resources()
sequence.load_task_resources(task)
resource_tool.load_resources()
def remove_work_calendar(ifc, work_calendar=None):
+1 -2
View File
@@ -726,7 +726,6 @@ class Sequence:
def get_recurrence_pattern_attributes(cls, recurrence_pattern): pass
def get_recurrence_pattern_times(cls): pass
def get_rel_sequence_attributes(cls): pass
def get_selected_resource(cls): pass
def get_start_date(cls): pass
def get_task_attribute_value(cls, attribute_name): pass
def get_task_attributes(cls): pass
@@ -758,7 +757,7 @@ class Sequence:
def load_task_inputs(cls, inputs): pass
def load_task_outputs(cls, outputs): pass
def load_task_properties(cls, task): pass
def load_task_resources(cls,resources): pass
def load_task_resources(cls, task): pass
def load_task_time_attributes(cls, task_time): pass
def load_task_tree(cls, work_schedule): pass
def load_work_calendar_attributes(cls, work_calendar): pass
@@ -0,0 +1,16 @@
param(
[string]$BlenderPath = "BLENDER_EXE"
)
Start-Process cmd -ArgumentList `
"/k ", `
"ASSOC .IFC=", `
"&", `
"FTYPE BLENDERBIM=""$BlenderPath"" --python-expr ""import bpy; bpy.ops.bim.load_project(filepath=r'%1')""",
"&", `
"echo. & echo. & echo To create an association between .IFC files and BlenderBIM", `
"&", `
"echo type the command below & echo.", `
"&", `
"echo ASSOC .IFC=BLENDERBIM & echo." `
-Verb RunAs
+10 -5
View File
@@ -168,7 +168,8 @@ class Blender:
"""
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
region = next(region for region in area.regions if region.type == "WINDOW")
context_override = {"area": area, "region": region}
space = next(space for space in area.spaces if space.type == "VIEW_3D")
context_override = {"area": area, "region": region, "space_data": space}
return context_override
@classmethod
@@ -483,6 +484,13 @@ class Blender:
if obj:
return obj
@classmethod
def lock_transform(cls, obj, lock_state=True):
for prop in ("lock_location", "lock_rotation", "lock_scale"):
attr = getattr(obj, prop)
for axis_idx in range(3):
attr[axis_idx] = lock_state
class Modifier:
@classmethod
def is_eligible_for_railing_modifier(cls, obj):
@@ -559,10 +567,7 @@ class Blender:
modifier_data = list(cls.get_modifiers_data(parent_element))[item]
children = cls.get_children_objects(modifier_data)
for child_obj in children:
for prop in ("lock_location", "lock_rotation", "lock_scale"):
attr = getattr(child_obj, prop)
for axis_idx in range(3):
attr[axis_idx] = lock_state
Blender.lock_transform(child_obj, lock_state)
@classmethod
def remove_constraints(cls, parent_element):
-1
View File
@@ -234,7 +234,6 @@ class Cost(blenderbim.core.tool.Cost):
@classmethod
def get_products(cls, related_object_type=None):
props = bpy.context.scene.BIMCostProperties
if related_object_type == "PRODUCT":
products = tool.Spatial.get_selected_products()
elif related_object_type == "PROCESS":
+2 -12
View File
@@ -249,17 +249,7 @@ class Drawing(blenderbim.core.tool.Drawing):
obj_data = obj.data
bpy.data.objects.remove(obj)
if obj_data and obj_data.users == 0: # in case we have drawing element types
cls.remove_object_data(obj_data)
@classmethod
def remove_object_data(cls, data):
"""also removes all related objects"""
if isinstance(data, bpy.types.Camera):
bpy.data.cameras.remove(data)
elif isinstance(data, bpy.types.Mesh):
bpy.data.meshes.remove(data)
elif isinstance(data, bpy.types.Curve):
bpy.data.curves.remove(data)
tool.Blender.remove_data_block(obj_data)
@classmethod
def delete_object(cls, obj):
@@ -445,7 +435,7 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def get_drawing_target_view(cls, drawing):
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("TargetView", "MODEL_VIEW")
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("TargetView", "MODEL_VIEW")
@classmethod
def get_group_elements(cls, group):
@@ -115,6 +115,12 @@ class Geometry(blenderbim.core.tool.Geometry):
for port in ifcopenshell.util.system.get_ports(element):
blenderbim.core.system.remove_port(tool.Ifc, tool.System, port=port)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
if isinstance(obj.data, bpy.types.Mesh) and not tool.Ifc.get_entity_by_id(
obj.data.BIMMeshProperties.ifc_definition_id
):
tool.Blender.remove_data_block(obj.data)
if is_spatial:
blenderbim.core.spatial.load_container_manager(tool.Spatial)
try:
+9
View File
@@ -86,6 +86,15 @@ class Ifc(blenderbim.core.tool.Ifc):
except:
pass
@classmethod
def get_entity_by_id(cls, entity_id):
"""useful to check whether entity_id is still exists in IFC"""
ifc_file = tool.Ifc.get()
try:
return ifc_file.by_id(entity_id)
except RuntimeError:
return None
@classmethod
def get_object(cls, element):
return IfcStore.get_element(element.id())
+48 -3
View File
@@ -540,11 +540,56 @@ class Model(blenderbim.core.tool.Model):
return axes
@classmethod
def regenerate_array(cls, parent, data, keep_objs=False):
tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent))
def handle_array_on_copied_element(cls, element, array_data=None):
"""if no `array_data` is provided then an array will be removed from the element"""
if array_data is None:
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not array_pset:
return
array_pset_data = array_pset["Data"]
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=array_pset)
# remove constraints
obj = tool.Ifc.get_object(element)
if not array_pset_data: # skip array parents
constraint = next((c for c in obj.constraints if c.type == "CHILD_OF"), None)
if constraint:
matrix = obj.matrix_world.copy()
obj.constraints.remove(constraint)
# keep the matrix before the constraint
# otherwise object will jump to some previous position
obj.matrix_world = matrix
tool.Blender.lock_transform(obj, False)
else:
obj = tool.Ifc.get_object(element)
array_pset = tool.Pset.get_element_pset(element, "BBIM_Array")
default_data = '[{"children": []}]'
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=array_pset,
properties={"Parent": element.GlobalId, "Data": default_data},
)
tool.Model.regenerate_array(obj, array_data)
json_data = json.dumps(array_data)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=array_pset, properties={"Data": json_data})
for i in range(len(array_data)):
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
@classmethod
def regenerate_array(cls, parent_obj, data, keep_objs=False):
tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj))
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj_stack = [parent]
obj_stack = [parent_obj]
for array in data:
if array["sync_children"]:
+7
View File
@@ -66,3 +66,10 @@ class Pset(blenderbim.core.tool.Pset):
if value is not None:
return False
return True
@classmethod
def enable_pset_editing(cls, pset_id=None, pset_name=None, pset_type=None, obj=None, obj_type=None):
#TODO REFACTOR ONCE toll/CORE functions are available
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=tool.Pset.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type
)
+2 -1
View File
@@ -61,6 +61,7 @@ class Resource(blenderbim.core.tool.Resource):
continue
create_new_resource_li(resource, 0)
cls.load_productivity_data()
cls.load_resource_properties()
props.is_resource_update_enabled = True
props.is_editing = True
@@ -425,7 +426,7 @@ class Resource(blenderbim.core.tool.Resource):
contracted_resources.remove(ancestor)
bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources)
cls.load_resources()
cls.load_resource_properties()
resource_props = bpy.context.scene.BIMResourceTreeProperties
expanded_resources = [item.ifc_definition_id for item in resource_props.resources]
+9 -18
View File
@@ -254,14 +254,6 @@ class Sequence(blenderbim.core.tool.Sequence):
return None
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id)
@classmethod
def get_selected_resource(cls):
if bpy.context.scene.BIMResourceTreeProperties.resources:
selected_resource_id = bpy.context.scene.BIMResourceTreeProperties.resources[
bpy.context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
return tool.Ifc.get().by_id(selected_resource_id)
@classmethod
def expand_task(cls, task):
props = bpy.context.scene.BIMWorkScheduleProperties
@@ -409,19 +401,17 @@ class Sequence(blenderbim.core.tool.Sequence):
return blenderbim.bim.helper.export_attributes(props.task_time_attributes, callback)
@classmethod
def load_task_resources(cls, resources):
def load_task_resources(cls, task):
props = bpy.context.scene.BIMWorkScheduleProperties
rprops = bpy.context.scene.BIMResourceProperties
props.task_resources.clear()
for resource in resources or []:
rprops.is_resource_update_enabled = False
for resource in cls.get_task_resources(task) or []:
new = props.task_resources.add()
new.ifc_definition_id = resource.id()
new.name = resource.Name or "Unnamed"
new.schedule_usage = resource.Usage.ScheduleUsage or 0 if resource.Usage else 0
@classmethod
def load_resources(cls):
blenderbim.core.resource.load_resources(tool.Resource)
cls.refresh_task_resources
rprops.is_resource_update_enabled = True
@classmethod
def get_task_inputs(cls, task):
@@ -447,6 +437,8 @@ class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def get_task_resources(cls, task):
if not task:
return
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources
return ifcopenshell.util.sequence.get_task_resources(task, is_deep)
@@ -1664,17 +1656,16 @@ class Sequence(blenderbim.core.tool.Sequence):
return
inputs = cls.get_task_inputs(task)
outputs = cls.get_task_outputs(task)
resources = cls.get_task_resources(task)
cls.load_task_inputs(inputs)
cls.load_task_outputs(outputs)
cls.load_task_resources(resources)
cls.load_task_resources(task)
@classmethod
def refresh_task_resources(cls):
task = cls.get_highlighted_task()
if not task:
return
cls.load_task_resources(cls.get_task_resources(task))
cls.load_task_resources(task)
@classmethod
def has_duration(cls, task):
+77
View File
@@ -182,3 +182,80 @@ class System(blenderbim.core.tool.System):
@classmethod
def set_active_system(cls, system):
bpy.context.scene.BIMSystemProperties.active_system_id = system.id()
@classmethod
def get_decoration_data(cls):
all_vertices = []
preview_edges = []
special_vertices = []
selected_edges = []
selected_vertices = []
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
start_vert_i = 0
if bpy.context.active_object and (active_element := tool.Ifc.get_entity(bpy.context.active_object)):
selected_elements = cls.get_connected_elements(active_element)
else:
selected_elements = set()
# TODO: get only objects visible in viewport
objects = set(bpy.data.objects) - set(bpy.data.collections["Types"].objects)
for obj in objects:
start_vert_i = len(all_vertices)
if obj.hide_get():
continue
if not isinstance(obj.data, bpy.types.Mesh):
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if not cls.is_mep_element(element):
continue
ports = tool.System.get_ports(element)
for port in ports:
position = tool.Model.get_element_matrix(port).translation * si_conversion
all_vertices.append(position)
verts = range(start_vert_i, start_vert_i + len(ports))
edges = [(i, i + 1) for i in range(start_vert_i, start_vert_i + len(ports) - 1)]
if element in selected_elements:
selected_vertices.extend(verts)
selected_edges.extend(edges)
else:
special_vertices.extend(verts)
preview_edges.extend(edges)
decoration_data = {
"all_vertices": all_vertices,
"preview_edges": preview_edges,
"special_vertices": [all_vertices[i] for i in special_vertices],
"selected_edges": selected_edges,
"selected_vertices": [all_vertices[i] for i in selected_vertices],
}
return decoration_data
@classmethod
def get_connected_elements(cls, element, elements=None):
if elements is None:
elements = set((element,))
connected_elements = ifcopenshell.util.system.get_connected_from(element)
connected_elements += ifcopenshell.util.system.get_connected_to(element)
for element in connected_elements:
if element in elements:
continue
elements.add(element)
cls.get_connected_elements(element, elements)
return elements
@classmethod
def is_mep_element(cls, element):
return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting")
@@ -61,6 +61,7 @@ Scenario: Add aggregate
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -73,6 +74,7 @@ Scenario: Add aggregate - with the aggregate inheriting the existing spatial col
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
@@ -86,6 +88,7 @@ Scenario: Add aggregate - add a nested aggregate
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
@@ -38,10 +38,12 @@ Scenario: Copy attribute to selected
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -357,6 +357,7 @@ Scenario: Assign cost item quantity - count based
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')"
@@ -371,6 +372,7 @@ Scenario: Assign cost item quantity - quantity based
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -388,6 +390,7 @@ Scenario: Unassign cost item quantity - selection based
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -404,6 +407,7 @@ Scenario: Unassign cost item quantity - explicit object
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -421,6 +425,7 @@ Scenario: Select cost item products
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -437,6 +442,7 @@ Scenario: Select Cost Schedule Products
And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -89,6 +89,7 @@ Scenario: Assign document
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When I press "bim.assign_document(document={reference})"
@@ -105,6 +106,7 @@ Scenario: Unassign document
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.assign_document(document={reference})"
@@ -5,6 +5,7 @@ Scenario: Duplicate drawing
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -17,6 +18,7 @@ Scenario: Create drawing
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -31,6 +33,7 @@ Scenario: Create drawing after deleting a duplicated object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -52,6 +55,7 @@ Scenario: Remove drawing
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -65,6 +69,7 @@ Scenario: Remove drawing - via object deletion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -79,6 +84,7 @@ Scenario: Remove drawing - deleting active drawing
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
@@ -5,6 +5,7 @@ Scenario: Edit object placement
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -15,6 +16,7 @@ Scenario: Add representation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -48,6 +50,7 @@ Scenario: Add representation - add a representation with a scale factor applied
And the object "Cube" is selected
When the object "Cube" is scaled to "2"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" has no scale
@@ -60,6 +63,7 @@ Scenario: Add representation - add a representation with a scale factor removed
And the object "Cube" is selected
When the object "Cube" is scaled to "2"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" has no scale
@@ -69,6 +73,7 @@ Scenario: Switch representation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
@@ -79,6 +84,7 @@ Scenario: Switch representation - current edited representation is updated prior
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.ContextType == 'Plan' and c.ContextIdentifier=='Annotation'][0].id()"
@@ -96,6 +102,7 @@ Scenario: Switch representation - current edited representation is discarded if
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the object "IfcWall/Cube" is scaled to "2"
@@ -111,6 +118,7 @@ Scenario: Switch representation - existing Blender modifiers must be purged
And I add a cube
And the object "Cube" is selected
And I add an array modifier
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
@@ -122,6 +130,7 @@ Scenario: Remove representation - remove an active representation
And I add a cube
And the object "Cube" is selected
And I add an array modifier
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
@@ -133,6 +142,7 @@ Scenario: Remove representation - remove an unloaded representation
And I add a cube
And the object "Cube" is selected
And I add an array modifier
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[1].id()"
@@ -182,6 +192,7 @@ Scenario: Update representation - updating a tessellation
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.update_representation(obj='IfcWall/Cube')"
@@ -192,6 +203,7 @@ Scenario: Update representation - updating a layered extrusion
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -219,6 +231,7 @@ Scenario: Update representation - updating a profiled extrusion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -242,6 +255,7 @@ Scenario: Get representation IFC parameters
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.update_representation(ifc_representation_class='IfcExtrudedAreaSolid/IfcRectangleProfileDef')"
@@ -252,6 +266,7 @@ Scenario: Copy representation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
@@ -273,6 +288,7 @@ Scenario: Override delete - with active IFC data
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -297,6 +313,7 @@ Scenario: Override duplicate move - with active IFC data
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -315,6 +332,7 @@ Scenario: Override duplicate move - copying a coloured representation
And I add a cube
And the object "Cube" is selected
And I add a material
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -347,6 +365,7 @@ Scenario: Override duplicate move - copying a layered extrusion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -376,6 +395,7 @@ Scenario: Override duplicate move - copying a profiled extrusion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -412,6 +432,7 @@ Scenario: Override duplicate move linked - with active IFC data
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -440,6 +461,7 @@ Scenario: Override paste buffer - with active IFC data
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -113,6 +113,7 @@ Scenario: Assign library reference
And the variable "reference" is "{ifc}.by_type('IfcLibraryReference')[-1].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -128,6 +129,7 @@ Scenario: Unassign library reference
And the variable "reference" is "{ifc}.by_type('IfcLibraryReference')[-1].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -72,6 +72,7 @@ Scenario: Assign material - single material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -83,6 +84,7 @@ Scenario: Unassign material - single material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -95,6 +97,7 @@ Scenario: Enable editing assigned material - single material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -107,6 +110,7 @@ Scenario: Disable editing assigned material - single material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -120,6 +124,7 @@ Scenario: Edit assigned material - single material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -160,6 +165,7 @@ Scenario: Unassign material - removing inherited material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
@@ -186,6 +192,7 @@ Scenario: Enable editing assigned material - material layer set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -203,6 +210,7 @@ Scenario: Disable editing assigned material - material layer set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -221,6 +229,7 @@ Scenario: Edit assigned material - material layer set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -266,6 +275,7 @@ Scenario: Enable editing assigned material - material profile set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -283,6 +293,7 @@ Scenario: Disable editing assigned material - material profile set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -301,6 +312,7 @@ Scenario: Edit assigned material - material profile set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -320,6 +332,7 @@ Scenario: Assign material - material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -331,6 +344,7 @@ Scenario: Unassign material - material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -343,6 +357,7 @@ Scenario: Enable editing assigned material - material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -355,6 +370,7 @@ Scenario: Disable editing assigned material - material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -368,6 +384,7 @@ Scenario: Edit assigned material - material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -419,6 +436,7 @@ Scenario: Add material set layer
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -438,6 +456,7 @@ Scenario: Remove material set layer
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
@@ -28,6 +28,7 @@ Scenario: Resize to storey
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -40,6 +41,7 @@ Scenario: Split along edge
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a plane of size "4" at "0,0,0"
@@ -284,6 +284,7 @@ Scenario: Assign actor
And I press "bim.add_actor"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "actor" is "{ifc}.by_type('IfcActor')[0].id()"
@@ -297,6 +298,7 @@ Scenario: Unassign actor
And I press "bim.add_actor"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "actor" is "{ifc}.by_type('IfcActor')[0].id()"
@@ -436,6 +436,7 @@ Scenario: Export IFC - with changed object scale synchronised
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -450,6 +451,7 @@ Scenario: Export IFC - with changed style colour synchronised
And I add a cube
And the object "Cube" is selected
And I add a material
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -464,6 +466,7 @@ Scenario: Export IFC - with changed style element synchronised
And I add a cube
And the object "Cube" is selected
And I add a material
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -5,6 +5,7 @@ Scenario: Add pset - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -16,11 +17,13 @@ Scenario: Add pset - multiple objects
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -33,6 +36,7 @@ Scenario: Enable pset editing - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -48,6 +52,7 @@ Scenario: Enable pset editing - material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -127,6 +132,7 @@ Scenario: Disable pset editing - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -139,6 +145,7 @@ Scenario: Disable pset editing - material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -204,6 +211,7 @@ Scenario: Edit pset - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -216,6 +224,7 @@ Scenario: Edit qto - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -229,6 +238,7 @@ Scenario: Edit pset - material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
@@ -294,10 +304,12 @@ Scenario: Copy property to selected - copy property
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -312,6 +324,7 @@ Scenario: Remove pset - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -327,11 +340,13 @@ Scenario: Remove pset - multiple objects
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -274,6 +274,7 @@ Scenario: Calculate Resource Work
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -307,6 +308,7 @@ Scenario: Assign Resource
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -322,6 +324,7 @@ Scenario: UnAssign Resource
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -5,6 +5,7 @@ Scenario: Reassign class
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I press "object.duplicate_move"
@@ -20,6 +21,7 @@ Scenario: Unlink object
And I add a cube
And the object "Cube" is selected
And I add a material
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material"
@@ -32,6 +34,7 @@ Scenario: Copy class
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When I press "bim.copy_class(obj='IfcWall/Cube')"
@@ -41,6 +44,7 @@ Scenario: Assign a class to a cube
Given an empty IFC project
And I add a cube
When the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" is an "IfcWall"
@@ -87,6 +91,7 @@ Scenario: Assign a class to a cube in a collection
And I add a cube
When the object "Cube" is selected
And the object "Cube" is placed in the collection "IfcBuildingStorey/My Storey"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
Then the object "IfcWall/Cube" is contained in "My Storey"
@@ -95,6 +100,7 @@ Scenario: Copy a wall
Given an empty IFC project
And I add a cube
When the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I duplicate the selected objects
@@ -5,6 +5,7 @@ Scenario: Select all walls
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a new item to "scene.IfcSelectorProperties.groups"
@@ -349,6 +349,8 @@ Scenario: Animate the construction of a wall
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -385,6 +387,7 @@ Scenario: Animate the demolition of a wall
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -424,6 +427,7 @@ Scenario: Animate the operation of a wall
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -458,6 +462,7 @@ Scenario: Animate the movement of a wall
And I add a cube
And I rename the object "Cube" to "ToObject"
And the object "ToObject" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/ToObject" is selected
@@ -508,6 +513,7 @@ Scenario: Animate the consumption of a wall
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -549,6 +555,7 @@ Scenario: Clear Previous Animation
And I press "bim.edit_task_time"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
@@ -758,6 +765,7 @@ Scenario: Assign Product Output
And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And the object "IfcWall/Cube" is selected
@@ -773,10 +781,11 @@ Scenario: Assign Product Input
And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And the object "IfcWall/Cube" is selected
And I press "bim.assign_process(task={task}, related_object_type='PRODUCT')"
And I press "bim.assign_process(task={task},related_object=0, related_object_type='PRODUCT')"
Then nothing happens
Scenario: Select Assigned Outputs
@@ -788,9 +797,9 @@ Scenario: Select Assigned Outputs
And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I press "object.select_all(action='DESELECT')"
And I press "bim.assign_class()"
And I press "bim.assign_product(task={task})"
When I press "bim.select_task_related_products(task={task})"
Then nothing happens
@@ -804,9 +813,10 @@ Scenario: Select Assigned Inputs
And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I press "object.select_all(action='DESELECT')"
And I press "bim.assign_class()"
And the object "IfcWall/Cube" is selected
And I press "bim.assign_process(task={task}, related_object_type='PRODUCT')"
When I press "bim.select_task_related_products(task={task})"
Then nothing happens
@@ -849,6 +859,7 @@ Scenario: Add Animation Camera
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_animation_camera"
@@ -21,7 +21,9 @@ Scenario: Assign container
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
@@ -32,7 +34,9 @@ Scenario: Copy to container
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
@@ -43,7 +47,9 @@ Scenario: Reference structure
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
@@ -54,7 +60,9 @@ Scenario: Dereference structure
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
@@ -66,7 +74,9 @@ Scenario: Select container
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
@@ -78,7 +88,9 @@ Scenario: Select similar container
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I press "bim.enable_editing_container"
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
@@ -24,6 +24,7 @@ Scenario: Enable editing type
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When I press "bim.enable_editing_type"
@@ -33,6 +34,7 @@ Scenario: Disable editing type
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.enable_editing_type"
@@ -43,6 +45,7 @@ Scenario: Assign type - assign to an empty type
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -58,6 +61,7 @@ Scenario: Assign type - assign to a type with representation maps
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
@@ -73,6 +77,7 @@ Scenario: Assign type - assign to a type with a material layer set, which automa
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -94,6 +99,7 @@ Scenario: Assign type - assign to a different type with a material layer set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -132,6 +138,7 @@ Scenario: Assign type - assign to a type with a material profile set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -155,6 +162,7 @@ Scenario: Select type objects
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -172,10 +180,12 @@ Scenario: Select similar type
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add an empty
@@ -6,6 +6,7 @@ Scenario: Add an opening
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
@@ -19,6 +20,7 @@ Scenario: Add an opening using the BIM tool
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -32,6 +34,7 @@ Scenario: Show openings
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -47,6 +50,7 @@ Scenario: Hide openings
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -62,6 +66,7 @@ Scenario: Edit openings
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -79,6 +84,7 @@ Scenario: Add an opening to Element B with a void that already voids Element A
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
@@ -105,6 +111,7 @@ Scenario: Remove opening
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -123,6 +130,7 @@ Scenario: Remove opening - using deletion
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
@@ -140,6 +148,7 @@ Scenario: Remove opening - indirectly by deleting its building element
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_potential_opening"
-8
View File
@@ -39,11 +39,3 @@ class TestResizeToStorey:
misc.get_object_storey("obj").should_be_called().will_return("storey")
misc.get_storey_height_in_si("storey", 1).should_be_called().will_return(None)
subject.resize_to_storey(misc, obj="obj", total_storeys=1)
class TestSplitAlongEdge:
def test_run(self, misc):
misc.split_objects_with_cutter(["obj"], "cutter").should_be_called().will_return(["new_obj"])
misc.run_root_copy_class(obj="new_obj").should_be_called()
misc.mark_object_as_edited("obj").should_be_called()
subject.split_along_edge(misc, cutter="cutter", objs=["obj"])
+29
View File
@@ -20,6 +20,7 @@ import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
import numpy as np
from test.bim.bootstrap import NewFile
from blenderbim.tool.model import Model as subject
@@ -50,3 +51,31 @@ class TestGenerateOccurrenceName(NewFile):
bpy.context.scene.BIMModelProperties.occurrence_name_style = "CUSTOM"
bpy.context.scene.BIMModelProperties.occurrence_name_function = '"Foobar"'
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
class TestGetManualBooleans(NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Model)
def test_len_returned_boolean(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
length = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT")
ifcopenshell.api.run("unit.assign_unit", ifc, units=[length])
ifcopenshell.api.run("unit.assign_unit", ifc)
element = ifc.createIfcColumn()
hea100 = ifc.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
model3d = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
body = ifcopenshell.api.run("context.add_context", ifc,context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
representation = ifcopenshell.api.run("geometry.add_profile_representation", ifc, context=body, profile=hea100, depth=5)
ifcopenshell.api.run("geometry.assign_representation", ifc, product=element, representation=representation)
matrix = np.eye(4)
matrix = ifcopenshell.util.placement.rotation(45,"X") @ matrix
matrix[:,3][0:3] = (0, 0, 3)
matrix = matrix.tolist()
ifcopenshell.api.run("geometry.add_boolean", ifc, representation = representation, type = "IfcHalfSpaceSolid", matrix = matrix)
assert len(subject.get_manual_booleans(element)) == 1