Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0

This commit is contained in:
Carlos Dias
2023-07-18 09:22:32 -03:00
committed by GitHub
110 changed files with 1574 additions and 601 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ elif sys.argv[1] == "--pyver?":
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
re_blender_python_version_maj_min = r"^SET\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)$"
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
html_txt = str(resp.read())
+29 -17
View File
@@ -95,6 +95,7 @@ classes = [
operator.OpenUpstream,
operator.OpenUri,
operator.SwitchTab,
operator.SetTab,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
@@ -125,24 +126,37 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# Scene panel groups
ui.BIM_PT_root,
# Project overview
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_collaboration,
ui.BIM_PT_selection,
ui.BIM_PT_geometry,
ui.BIM_PT_services,
ui.BIM_PT_structural,
ui.BIM_PT_4D5D,
ui.BIM_PT_quality_control,
ui.BIM_PT_integrations,
# Object panel groups
ui.BIM_PT_object_metadata,
ui.BIM_PT_geometry_object,
ui.BIM_PT_services_object,
ui.BIM_PT_utilities_object,
ui.BIM_PT_misc_object,
ui.BIM_PT_selection,
# Tabs panel
ui.BIM_PT_tabs,
# Object information
ui.BIM_PT_tab_object_metadata,
ui.BIM_PT_tab_misc,
# Geometry and materials
ui.BIM_PT_tab_representations,
ui.BIM_PT_tab_geometric_relationships,
ui.BIM_PT_tab_parametric_geometry,
ui.BIM_PT_tab_materials,
ui.BIM_PT_tab_styles,
# Drawings and documents
# Services and systems
ui.BIM_PT_tab_services,
ui.BIM_PT_tab_services_object,
# Structural analysis
ui.BIM_PT_tab_structural,
# Construction scheduling
ui.BIM_PT_tab_4D5D,
# Facility management
ui.BIM_PT_tab_handover,
ui.BIM_PT_tab_operations,
# Quality and coordination
ui.BIM_PT_tab_quality_control,
ui.BIM_PT_tab_collaboration,
ui.BIM_PT_tab_integrations,
]
for mod in modules.values():
@@ -170,9 +184,7 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.undo_pre.append(handler.undo_pre)
bpy.app.handlers.undo_post.append(handler.undo_post)
bpy.app.handlers.redo_pre.append(handler.redo_pre)
bpy.app.handlers.redo_post.append(handler.redo_post)
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
@@ -41,4 +41,17 @@
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="SurveyArea">
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-CONTROLPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-TRAVERSEPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
</g>
<g id="SurveyArea-SPOTELEVATION">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

+7 -17
View File
@@ -54,10 +54,12 @@ def mode_callback(obj, data):
def name_callback(obj, data):
# TODO Do we still need this, now that we are monitoring the undo redo objects?
try:
obj.name
except:
# The object is invalid but somehow still has a callback. Clear all
# msgbus subscriptions to prevent useless further triggers.
bpy.msgbus.clear_by_owner(obj)
return # In case the object RNA is gone during an undo / redo operation
# Blender names are up to 63 UTF-8 bytes
if len(bytes(obj.name, "utf-8")) >= 63:
@@ -203,34 +205,22 @@ def loadIfcStore(scene):
IfcStore.relink_all_objects()
@persistent
def undo_pre(scene):
IfcStore.track_undo_redo_stack_object_map()
@persistent
def undo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
def redo_pre(scene):
IfcStore.track_undo_redo_stack_object_map()
tool.Ifc.rebuild_element_maps()
@persistent
def redo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
tool.Ifc.rebuild_element_maps()
def get_application(ifc):
+6
View File
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# from datetime import date
import re
import bpy
import json
import math
@@ -31,6 +32,11 @@ import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
def uncamel(s):
s = re.sub('([a-z0-9])([A-Z])', r'\1 \2', s[3:] if s.startswith("Ifc") else s)
return re.sub('([A-Z])([A-Z][a-z])', r'\1 \2', s)
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
"""you can set attribute active in popup with `active_attribute`
meaning you will be able to type into attribute's field without having to click
+34 -109
View File
@@ -44,9 +44,6 @@ class IfcStore:
classification_file = None
library_path = ""
library_file = None
element_listeners = set()
undo_redo_stack_objects = set()
undo_redo_stack_object_names = {}
current_transaction = ""
last_transaction = ""
history = []
@@ -161,96 +158,6 @@ class IfcStore:
return
return obj
@staticmethod
def add_element_listener(callback):
IfcStore.element_listeners.add(callback)
@staticmethod
def track_undo_redo_stack_object_map():
"""Keeps track of currently mapped object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur whenever an object is affected during an operation.
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
"""
for key, value in IfcStore.id_map.items():
try:
IfcStore.undo_redo_stack_object_names[key] = value.name
except:
continue
@staticmethod
def track_undo_redo_stack_selected_objects():
"""Keeps track of selected object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children. This is
important because selected objects are often deleted from the scene.
So if I first select o1, then o2, then o3, then press undo, o3 will be
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
"""
if bpy.context.active_object:
objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
objects.update([o.name for o in bpy.context.active_object.children])
else:
objects = set([o.name for o in bpy.context.selected_objects])
for obj in bpy.context.selected_objects:
objects.update([o.name for o in obj.children])
IfcStore.undo_redo_stack_objects |= objects
@staticmethod
def reload_undo_redo_stack_objects():
"""Reloads any invalidated objects after undo or redo
After an undo or redo operation, objects may have been invalidated in
our id_map and guid_map. Invalidated objects are typically those that
have been manipulated or deleted. This checks the cache of mapped and
selected objects prior to the operation and ensures that if the object
is invalidated, they are reloaded based on the object name that was
tracked prior to the undo / redo.
"""
file = IfcStore.get_file()
if not file:
return
# First, reload objects that were selected or active
for name in IfcStore.undo_redo_stack_objects:
obj = bpy.data.objects.get(name)
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
for key, value in IfcStore.id_map.items():
try:
value.name
except:
# TODO not so sure about this obj_name check
obj_name = IfcStore.undo_redo_stack_object_names.get(key, None)
if not obj_name:
continue
obj = bpy.data.objects.get(obj_name)
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
if not IfcStore.get_file():
@@ -283,6 +190,13 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
# Please use tool.Ifc.link() instead of this method. We want to
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
if isinstance(obj, bpy.types.Mesh):
obj.BIMMeshProperties.ifc_definition_id = element.id()
return
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
@@ -309,9 +223,6 @@ class IfcStore:
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
for listener in IfcStore.element_listeners:
listener(element, obj)
if IfcStore.history:
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
IfcStore.history[-1]["operations"].append(
@@ -332,10 +243,12 @@ class IfcStore:
IfcStore.id_map[data["id"]] = obj
if "guid" in data:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@@ -435,8 +348,6 @@ class IfcStore:
@staticmethod
def begin_transaction(operator):
IfcStore.undo_redo_stack_objects = set()
IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@@ -462,21 +373,35 @@ class IfcStore:
IfcStore.future = []
@staticmethod
def undo():
def undo(until_key=None):
BrickStore.undo()
if not IfcStore.history:
return
event = IfcStore.history.pop()
for transaction in event["operations"][::-1]:
transaction["rollback"](transaction["data"])
IfcStore.future.append(event)
while IfcStore.history:
if IfcStore.history[-1]["key"] == until_key:
return
event = IfcStore.history.pop()
for transaction in event["operations"][::-1]:
transaction["rollback"](transaction["data"])
IfcStore.future.append(event)
@staticmethod
def redo():
def redo(until_key=None):
BrickStore.redo()
if not IfcStore.future:
return
event = IfcStore.future.pop()
for transaction in event["operations"]:
transaction["commit"](transaction["data"])
IfcStore.history.append(event)
has_encountered_key = False
while IfcStore.future:
if has_encountered_key and IfcStore.future[-1]["key"] != until_key:
return
elif IfcStore.future[-1]["key"] == until_key:
has_encountered_key = True
event = IfcStore.future.pop()
for transaction in event["operations"]:
transaction["commit"](transaction["data"])
IfcStore.history.append(event)
+12 -2
View File
@@ -692,7 +692,7 @@ class IfcImporter:
mesh = self.create_native_faceted_brep(element, mesh_name)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name)
mesh.BIMMeshProperties.ifc_definition_id = representation.id()
tool.Ifc.link(representation, mesh)
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
@@ -921,7 +921,7 @@ class IfcImporter:
return result
def create_pointcloud(self, product, representation):
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
@@ -937,6 +937,7 @@ class IfcImporter:
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata(vertex_list, [], [])
tool.Ifc.link(representation, mesh)
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
@@ -1342,12 +1343,21 @@ class IfcImporter:
last_obj = obj
if not last_obj:
return
# temporarily unhide types collection to make sure all objects will be cleaned
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
types_collection = project_collection.children[self.type_collection.name]
types_collection.hide_viewport = False
bpy.context.view_layer.objects.active = last_obj
context_override = {}
bpy.ops.object.editmode_toggle(context_override)
bpy.ops.mesh.tris_convert_to_quads(context_override)
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
types_collection.hide_viewport = True
bpy.context.view_layer.objects.active = last_obj
IfcStore.edited_objs.clear()
def load_file(self):
@@ -22,13 +22,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_aggregate(Panel):
bl_label = "IFC Aggregates"
bl_label = "Aggregates"
bl_idname = "BIM_PT_aggregate"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -54,12 +54,12 @@ def draw_ui(context, layout, obj_type, attributes):
class BIM_PT_object_attributes(Panel):
bl_label = "IFC Attributes"
bl_label = "Attributes"
bl_idname = "BIM_PT_object_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -76,7 +76,7 @@ class BIM_PT_object_attributes(Panel):
class BIM_PT_material_attributes(Panel):
bl_label = "IFC Material Attributes"
bl_label = "Material Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -26,7 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
layout = self.layout
@@ -29,7 +29,7 @@ class BIM_PT_bcf(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -27,7 +27,7 @@ class BIM_PT_qa(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
self.layout.use_property_split = True
@@ -25,7 +25,7 @@ from blenderbim.bim.module.boundary.data import SpaceBoundariesData
class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_label = "Space Boundaries"
bl_id_name = "BIM_PT_scene_boundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -45,12 +45,12 @@ class BIM_PT_SceneBoundaries(Panel):
class BIM_PT_Boundary(Panel):
bl_label = "IFC Space Boundary"
bl_label = "Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -117,14 +117,14 @@ class BIM_PT_Boundary(Panel):
class BIM_PT_SpaceBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_label = "Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -26,14 +26,10 @@ from blenderbim.tool.brick import BrickStore
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
bl_idname = "BIM_PT_brickschema"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "OTHER")
bl_parent_id = "BIM_PT_tab_operations"
def draw(self, context):
if not BrickschemaData.is_loaded:
@@ -108,7 +104,7 @@ class BIM_PT_brickschema(Panel):
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "IFC Brickschema References"
bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_ifcclash(Panel):
bl_label = "IFC Clash Sets"
bl_label = "Clash Sets"
bl_idname = "BIM_PT_ifcclash"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
@@ -30,7 +30,7 @@ from blenderbim.bim.module.classification.data import (
class BIM_PT_classifications(Panel):
bl_label = "IFC Classifications"
bl_label = "Classifications"
bl_idname = "BIM_PT_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -156,13 +156,13 @@ class ReferenceUI:
class BIM_PT_classification_references(Panel, ReferenceUI):
bl_label = "IFC Classification References"
bl_label = "Classification References"
bl_idname = "BIM_PT_classification_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -180,7 +180,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
class BIM_PT_material_classifications(Panel, ReferenceUI):
bl_label = "IFC Material Classifications"
bl_label = "Material Classifications"
bl_idname = "BIM_PT_material_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -206,7 +206,7 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
class BIM_PT_cost_classifications(Panel, ReferenceUI):
bl_label = "IFC Cost Classifications"
bl_label = "Cost Classifications"
bl_idname = "BIM_PT_cost_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -16,18 +16,18 @@
# 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 = "IFC COBie"
bl_label = "COBie"
bl_idname = "BIM_PT_cobie"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_handover"
def draw(self, context):
layout = self.layout
@@ -23,7 +23,7 @@ from blenderbim.bim.module.constraint.data import ConstraintsData, ObjectConstra
class BIM_PT_constraints(Panel):
bl_label = "IFC Constraints"
bl_label = "Constraints"
bl_idname = "BIM_PT_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -68,14 +68,14 @@ class BIM_PT_constraints(Panel):
class BIM_PT_object_constraints(Panel):
bl_label = "IFC Constraints"
bl_label = "Constraints"
bl_idname = "BIM_PT_object_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -23,7 +23,7 @@ from blenderbim.bim.module.context.data import ContextData
class BIM_PT_context(bpy.types.Panel):
bl_label = "IFC Geometric Representation Contexts"
bl_label = "Geometric Representation Contexts"
bl_idname = "BIM_PT_context"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -24,13 +24,13 @@ from blenderbim.bim.module.cost.data import CostSchedulesData
class BIM_PT_cost_schedules(Panel):
bl_label = "IFC Cost Schedules"
bl_label = "Cost Schedules"
bl_idname = "BIM_PT_cost_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -267,7 +267,7 @@ class BIM_PT_cost_schedules(Panel):
class BIM_PT_cost_item_types(Panel):
bl_label = "IFC Cost Item Types"
bl_label = "Cost Item Types"
bl_idname = "BIM_PT_cost_item_types"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -353,7 +353,7 @@ class BIM_PT_cost_item_types(Panel):
class BIM_PT_cost_item_quantities(Panel):
bl_label = "IFC Cost Item Quantities"
bl_label = "Cost Item Quantities"
bl_idname = "BIM_PT_cost_item_quantities"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -525,7 +525,7 @@ class BIM_PT_cost_item_quantities(Panel):
class BIM_PT_cost_item_rates(Panel):
bl_label = "IFC Cost Item Rates"
bl_label = "Cost Item Rates"
bl_idname = "BIM_PT_cost_item_rates"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -26,7 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
layout = self.layout
@@ -21,13 +21,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_ifccsv(Panel):
bl_label = "IFC CSV Import/Export"
bl_label = "CSV Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_debug(Panel):
bl_label = "IFC Debug"
bl_label = "Debug"
bl_idname = "BIM_PT_debug"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
@@ -24,13 +24,13 @@ import json
class BIM_PT_diff(Panel):
bl_label = "IFC Diff"
bl_label = "Diff"
bl_idname = "BIM_PT_diff"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
if not DiffData.is_loaded:
@@ -23,7 +23,7 @@ from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
class BIM_PT_documents(Panel):
bl_label = "IFC Documents"
bl_label = "Documents"
bl_idname = "BIM_PT_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -76,14 +76,14 @@ class BIM_PT_documents(Panel):
class BIM_PT_object_documents(Panel):
bl_label = "IFC Documents"
bl_label = "Documents"
bl_idname = "BIM_PT_object_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -748,7 +748,7 @@ class AngleDecorator(BaseDecorator):
arcs_color = None
edges_color = UNSPECIAL_ELEMENT_COLOR
if context.object == obj and obj.data.is_editmode:
if context.active_object == obj and obj.data.is_editmode:
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
edges_color = None
@@ -192,6 +192,8 @@ def format_distance(
tx_dist += str(feet) + "'"
if feet and add_inches:
tx_dist += " - "
if not feet and value < 0:
tx_dist += "-"
if add_inches:
tx_dist += str(inches)
if add_inches and frac:
@@ -23,6 +23,7 @@ import json
import time
import bmesh
import shutil
import hashlib
import shapely
import subprocess
import webbrowser
@@ -365,7 +366,6 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_linework(self, context):
global ifcopenshell
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
return
svg_path = self.get_svg_path(cache_type="linework")
@@ -395,10 +395,6 @@ class CreateDrawing(bpy.types.Operator):
}
cached_linework -= edited_guids
# This is a work in progress. See #1153 and #1564.
import hashlib
import ifcopenshell.draw
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
for ifc_path, ifc in files.items():
@@ -569,7 +565,7 @@ class CreateDrawing(bpy.types.Operator):
for projection in projections:
boundary_lines = []
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
boundary_lines.append(shapely.LineString([start, end]))
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
@@ -896,6 +892,42 @@ class CreateDrawing(bpy.types.Operator):
classes.append("cut")
el.set("class", " ".join(classes))
# An element group will contain a bunch of paths representing the
# cut of that element. However IfcOpenShell may not correctly
# create closed paths. We post-process all paths with shapely to
# ensure things that should be closed (i.e.
# shapely.polygonize_full) are, and things which aren't are left
# alone (e.g. dangles, cuts, invalids). See #3421.
line_strings = []
old_paths = []
for path in el.findall("{http://www.w3.org/2000/svg}path"):
for subpath in path.attrib["d"].split("M")[1:]:
subpath = "M" + subpath.strip()
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
line_strings.append(shapely.LineString(coords))
old_paths.append(path)
unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
if hasattr(unioned_line_strings, "geoms"):
results = shapely.polygonize_full(unioned_line_strings.geoms)
else:
results = []
# If we succeeded in generating new path geometry, remove all the
# old paths and add new ones.
if results:
for path in old_paths:
path.getparent().remove(path)
for result in results:
for geom in result.geoms:
path = etree.SubElement(el, "path")
if isinstance(geom, shapely.Polygon):
d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.exterior.coords[0:-1]]) + " Z"
for interior in geom.interiors:
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
elif isinstance(geom, shapely.LineString):
d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.coords]) + " Z"
path.attrib["d"] = d
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
@@ -332,9 +332,10 @@ class SvgWriter:
return
classes = self.get_attribute_classes(obj)
if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes)
return
if len(obj.data.vertices) and not len(obj.data.edges):
return self.draw_point_annotation(obj, classes)
elif len(obj.data.polygons) == 0:
return self.draw_edge_annotation(obj, classes)
bm = bmesh.new()
bm.from_mesh(obj.data)
@@ -501,9 +502,9 @@ class SvgWriter:
return pattern
def get_scale(size, direction):
vector = direction * size
shrinked_vector = size // segment_width * segment_width * direction
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
original_edge = direction * size
current_svg_segments = ceil(size / segment_width) * segment_width * direction
scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)]
return "scale(%f, %f)" % (scale[0], scale[1])
def poly_to_edges(poly):
@@ -557,11 +558,16 @@ class SvgWriter:
pattern_dir = pattern_edge.normalized()
pattern_length = pattern_edge.length
segments = int(pattern_length // segment_width)
segments = ceil(pattern_length / segment_width)
pattern_dir_step = pattern_dir * segment_width
points = [pattern_dir_step * i for i in range(segments)]
# it takes atleast 2 points to preserve the edge direction
# if there is just 1 segment then we still add second point and then hide the "marker-end"
n_points = max(segments, 2)
points = [pattern_dir_step * i for i in range(n_points)]
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
polyline_style = f"marker: url(#{marker_id}); stroke: none; "
if segments == 1:
polyline_style += "marker-end: none; "
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
polyline = self.svg.polyline(
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
@@ -837,6 +843,26 @@ class SvgWriter:
self.svg.add(tag)
line_number += len(tag.elements)
def draw_point_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
element = tool.Ifc.get_entity(obj)
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if point_type:
svg_id += f"-{point_type}"
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -880,8 +906,13 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
vector = projected_points_svg[1] - projected_points_svg[0]
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
text_dir = projected_points_svg[1] - projected_points_svg[0]
if text_dir.x < 0:
box_alignment = "bottom-right"
text_dir *= -1
else:
box_alignment = "bottom-left"
angle = math.degrees(text_dir.angle_signed(Vector((1, 0))))
# TODO: allow metric to be configurable
def get_text():
@@ -895,7 +926,6 @@ class SvgWriter:
text = "{}{}".format("" if z < 0 else "+", rl)
return text
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
self.draw_dimension_text(
get_text,
description,
@@ -931,7 +961,7 @@ class SvgWriter:
p0 = points_2d[1] + dir0 * angle_radius
p2 = points_2d[1] + dir1 * angle_radius
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
# points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]]
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
bm = bmesh.new()
bm.verts.index_update()
@@ -387,7 +387,7 @@ class BIM_PT_sheets(Panel):
class BIM_PT_product_assignments(Panel):
bl_label = "IFC Product Assignments"
bl_label = "Product Assignments"
bl_idname = "BIM_PT_product_assignments"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -422,7 +422,7 @@ class BIM_PT_product_assignments(Panel):
class BIM_PT_text(Panel):
bl_label = "IFC Text"
bl_label = "Text"
bl_idname = "BIM_PT_text"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -101,6 +101,8 @@ class LaunchAnnotationTypeManager(bpy.types.Operator):
op = row.operator("bim.select_type", icon="OBJECT_DATA")
op.relating_type = relating_type["id"]
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
op.element = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = relating_type["id"]
op = row.operator("bim.remove_type", icon="X", text="")
@@ -227,7 +229,7 @@ class AnnotationToolUI:
@classmethod
def draw_edit_object_interface(cls, context):
if DecoratorData.get_ifc_text_data(bpy.context.object):
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
@classmethod
@@ -246,16 +248,16 @@ class AnnotationToolUI:
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
if object_type in ("TEXT", "STAIR_ARROW"):
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
add_layout_hotkey_operator(
cls.layout,
"Bulk Tag",
"S_T",
"Create new annotations and automatically adjust them to the selected objects",
)
add_layout_hotkey_operator(
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
)
add_layout_hotkey_operator(
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
)
class Hotkey(bpy.types.Operator, Operator):
@@ -287,23 +289,27 @@ class Hotkey(bpy.types.Operator, Operator):
def hotkey_S_T(self):
props = bpy.context.scene.BIMAnnotationProperties
object_type = props.object_type
annotation_type = props.object_type
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for tagging.")
return
related_objects = bpy.context.selected_objects
for related_object in related_objects:
create_annotation()
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
def hotkey_S_A(self):
create_annotation()
def hotkey_S_E(self):
if not bpy.context.object:
if not bpy.context.active_object:
return
if DecoratorData.get_ifc_text_data(bpy.context.object):
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
bpy.ops.bim.edit_text_popup()
def hotkey_S_G(self):
@@ -312,9 +318,15 @@ class Hotkey(bpy.types.Operator, Operator):
if not element or not element.is_a("IfcAnnotation"):
continue
annotation_type = element.ObjectType
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for readjustment.")
continue
related_product = tool.Drawing.get_assigned_product(element)
if not related_product:
self.report({"ERROR"}, "Selected annotation has no product assigned.")
continue
related_object = tool.Ifc.get_object(related_product)
tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
@@ -598,6 +598,9 @@ class OverrideDuplicateMove(bpy.types.Operator):
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
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.
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -1017,7 +1020,6 @@ class RefreshAggregate(bpy.types.Operator):
return parents
def duplicate_children(entity):
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
@@ -1236,6 +1238,7 @@ class OverridePasteBuffer(bpy.types.Operator):
class OverrideModeSetEdit(bpy.types.Operator):
bl_description = "Switch from Object mode to Edit mode"
bl_idname = "bim.override_mode_set_edit"
bl_label = "IFC Mode Set Edit"
bl_options = {"REGISTER", "UNDO"}
@@ -1244,7 +1247,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objs = context.selected_objects or ([context.active_object] if context.active_object else [])
selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
active_obj = context.active_object
if context.active_object:
@@ -1254,19 +1257,15 @@ class OverrideModeSetEdit(bpy.types.Operator):
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.enable_editing_boundary_geometry()
for obj in objs:
for obj in selected_objs:
if not obj:
continue
if not obj.data:
obj.select_set(False)
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
# We are switching from OBJECT to EDIT mode.
usage_type = tool.Model.get_usage_type(element)
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
# Parametric objects shall not be edited as meshes as they
@@ -1274,57 +1273,37 @@ class OverrideModeSetEdit(bpy.types.Operator):
# constraints.
obj.select_set(False)
continue
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
if (
tool.Pset.get_element_pset(element, "BBIM_Door")
or tool.Pset.get_element_pset(element, "BBIM_Window")
or tool.Pset.get_element_pset(element, "BBIM_Stair")
):
if tool.Blender.Modifier.is_modifier_with_non_editable_path(element):
obj.select_set(False)
continue
is_profile = True
if usage_type == "PROFILE":
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="A_E", description="")
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
obj.select_set(False)
continue
# TODO: refactor repetitive code
if tool.Pset.get_element_pset(element, "BBIM_Roof"):
if len(context.selected_objects) == 1:
bpy.ops.bim.enable_editing_roof_path()
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
obj.select_set(False)
continue
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
if len(context.selected_objects) == 1:
if obj.BIMRailingProperties.is_editing == 1:
self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
return {"FINISHED"}
bpy.ops.bim.enable_editing_railing_path()
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
obj.select_set(False)
continue
if (
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
elif (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
or tool.Geometry.is_swept_profile(representation)
):
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="S_E", description="")
operator = lambda: bpy.ops.bim.hotkey(hotkey="S_E")
elif tool.Blender.Modifier.is_editing_parameters(obj):
# This should go BEFORE the modifiers
self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
obj.select_set(False)
continue
elif tool.Blender.Modifier.is_roof(element):
operator = lambda: bpy.ops.bim.enable_editing_roof_path()
elif tool.Blender.Modifier.is_railing(element):
operator = lambda: bpy.ops.bim.enable_editing_railing_path()
else:
is_profile = False
if is_profile:
if len(context.selected_objects) == 1 and context.active_object == context.selected_objects[0]:
tool.Blender.select_and_activate_single_object(context, obj)
operator()
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
@@ -1350,17 +1329,18 @@ class OverrideModeSetEdit(bpy.types.Operator):
obj.select_set(False)
continue
if not context.selected_objects or len(context.selected_objects) != len(objs):
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.")
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
if context.active_object not in context.selected_objects:
# The active object is non-mesh-like. Set a valid object (or None) as active
context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None
if context.active_object:
return tool.Blender.toggle_edit_mode(context)
# Restore the selection if nothing worked
for obj in objs:
for obj in selected_objs:
obj.select_set(True)
context.view_layer.objects.active = active_obj
return {"FINISHED"}
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import prop_with_search
@@ -29,19 +30,21 @@ def object_menu(self, context):
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
def outliner_menu(self, context):
self.layout.separator()
self.layout.operator("bim.override_outliner_delete", icon='X')
self.layout.operator("bim.override_outliner_delete", icon="X")
class BIM_PT_representations(Panel):
bl_label = "IFC Representations"
bl_label = "Representations"
bl_idname = "BIM_PT_representations"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_representations"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -58,20 +61,24 @@ class BIM_PT_representations(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
if not RepresentationsData.data["representations"]:
layout.label(text="No representations found")
row = layout.row(align=True)
prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
if not RepresentationsData.data["representations"]:
layout.label(text="No Representations Found")
for representation in RepresentationsData.data["representations"]:
row = self.layout.row(align=True)
row.label(text=representation["ContextType"])
row.label(text=representation["ContextIdentifier"])
row.label(text=representation["TargetView"])
row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="")
op = row.operator(
"bim.switch_representation",
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
text="",
)
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = representation["id"]
@@ -80,14 +87,14 @@ class BIM_PT_representations(Panel):
class BIM_PT_connections(Panel):
bl_label = "IFC Connections"
bl_label = "Connections"
bl_idname = "BIM_PT_connections"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -118,7 +125,7 @@ class BIM_PT_connections(Panel):
class BIM_PT_mesh(Panel):
bl_label = "IFC Representation"
bl_label = "Representation"
bl_idname = "BIM_PT_mesh"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -185,7 +192,7 @@ def BIM_PT_transform(self, context):
class BIM_PT_derived_placements(Panel):
bl_label = "IFC Derived Placements"
bl_label = "Derived Placements"
bl_idname = "BIM_PT_derived_placements"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -220,7 +227,7 @@ class BIM_PT_derived_placements(Panel):
class BIM_PT_workarounds(Panel):
bl_label = "IFC Vendor Workarounds"
bl_label = "Vendor Workarounds"
bl_idname = "BIM_PT_workarounds"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -23,7 +23,7 @@ from blenderbim.bim.module.georeference.data import GeoreferenceData
class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing"
bl_label = "Georeferencing"
bl_idname = "BIM_PT_gis"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -20,13 +20,13 @@ from bpy.types import Panel
class BIM_PT_cityjson_converter(Panel):
bl_label = "IFC CityJSON"
bl_label = "CityJSON"
bl_idname = "BIM_PT_ifccityjson"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -22,7 +22,7 @@ from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
class BIM_PT_groups(Panel):
bl_label = "IFC Groups"
bl_label = "Groups"
bl_idname = "BIM_PT_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -69,14 +69,14 @@ class BIM_PT_groups(Panel):
class BIM_PT_object_groups(Panel):
bl_label = "IFC Groups"
bl_label = "Groups"
bl_idname = "BIM_PT_object_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_utilities_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -6,7 +6,7 @@ from blenderbim.bim.module.ifcgit.data import IfcGitData
class IFCGIT_PT_panel(bpy.types.Panel):
"""Scene Properties panel to interact with IFC repository data"""
bl_label = "IFC Git"
bl_label = "Git"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -22,13 +22,13 @@ from blenderbim.bim.module.layer.data import LayersData
class BIM_PT_layers(Panel):
bl_label = "IFC Presentation Layers"
bl_label = "Presentation Layers"
bl_idname = "BIM_PT_layers"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -27,7 +27,7 @@ class BIM_PT_lca(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
props = context.scene.BIMLCAProperties
@@ -23,7 +23,7 @@ from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesD
class BIM_PT_libraries(Panel):
bl_label = "IFC Libraries"
bl_label = "Libraries"
bl_idname = "BIM_PT_libraries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -94,14 +94,14 @@ class BIM_PT_libraries(Panel):
class BIM_PT_library_references(Panel):
bl_label = "IFC Library References"
bl_label = "Library References"
bl_idname = "BIM_PT_library_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
@@ -25,7 +26,7 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
class BIM_PT_materials(Panel):
bl_label = "IFC Materials"
bl_label = "Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -88,7 +89,7 @@ class BIM_PT_materials(Panel):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
bl_label = "IFC Material"
bl_label = "Material"
bl_idname = "BIM_PT_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -111,15 +112,17 @@ class BIM_PT_material(Panel):
class BIM_PT_object_material(Panel):
bl_label = "IFC Object Material"
bl_label = "Object Material"
bl_idname = "BIM_PT_object_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_materials"
@classmethod
def poll(cls, context):
if not tool.Blender.is_tab(context, "GEOMETRY"):
return False
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
@@ -67,6 +67,7 @@ class AuthoringData:
cls.data["active_material_usage"] = cls.active_material_usage()
cls.data["active_representation_type"] = cls.active_representation_type()
cls.data["boundary_class"] = cls.boundary_class()
cls.data["selected_material_usages"] = cls.selected_material_usages()
@classmethod
def boundary_class(cls):
@@ -238,6 +239,23 @@ class AuthoringData:
return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results]
return []
@classmethod
def selected_material_usages(cls):
selected_usages = {}
for obj in bpy.context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
usage = tool.Model.get_usage_type(element)
if not usage:
representation = tool.Geometry.get_active_representation(obj)
if representation and representation.RepresentationType == "SweptSolid":
usage = "SWEPTSOLID"
else:
continue
selected_usages.setdefault(usage, []).append(obj)
return selected_usages
class ArrayData:
data = {}
@@ -272,12 +290,27 @@ class StairData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"pset_data": cls.pset_data()}
cls.data = {}
cls.data["pset_data"] = cls.pset_data()
if not cls.data["pset_data"]:
return
cls.data["general_params"] = cls.general_params()
@classmethod
def pset_data(cls):
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Stair")
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMStairProperties
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_props_kwargs(stair_type=data["stair_type"])
for prop_name in general_props:
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
general_params[prop_readable_name] = prop_value
return general_params
class SverchokData:
data = {}
@@ -276,7 +276,7 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
def update_door_modifier_bmesh(context):
obj = context.object
obj = context.active_object
props = obj.BIMDoorProperties
overall_width = props.overall_width
@@ -456,7 +456,7 @@ def update_door_modifier_bmesh(context):
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=lining_offset_verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -475,9 +475,9 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a door.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
if context.active_object is not None:
spawn_location = context.active_object.location.copy()
context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -98,7 +98,7 @@ def add_object(self, context):
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_grid"
bl_label = "IFC Grid"
bl_label = "Grid"
bl_options = {"REGISTER", "UNDO"}
u_spacing: FloatProperty(name="U Spacing", default=10)
@@ -498,10 +498,12 @@ class AddBoolean(Operator, tool.Ifc.Operator):
bl_label = "Add Boolean"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return len(context.selected_objects) == 2
def _execute(self, context):
props = context.scene.BIMModelProperties
if len(context.selected_objects) != 2:
return {"FINISHED"}
obj1, obj2 = context.selected_objects
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
@@ -546,14 +548,19 @@ class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
bl_label = "Show Booleans"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
obj = context.active_object
return (
obj is not None
and obj.data
and hasattr(obj.data, "BIMMeshProperties")
and obj.data.BIMMeshProperties.ifc_definition_id
)
def _execute(self, context):
obj = context.active_object
if (
not obj.data
or not hasattr(obj.data, "BIMMeshProperties")
or not obj.data.BIMMeshProperties.ifc_definition_id
):
return {"FINISHED"}
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
booleans = []
@@ -92,7 +92,7 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
bl_label = "IFC Class"
bl_label = "Class"
def draw(self, context):
pie = self.layout.menu_pie()
@@ -99,7 +99,7 @@ class BIMModelProperties(PropertyGroup):
)
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
extrusion_depth: bpy.props.FloatProperty(default=42.0, subtype="DISTANCE")
extrusion_depth: bpy.props.FloatProperty(min=0.001, default=42.0, subtype="DISTANCE")
cardinal_point: bpy.props.EnumProperty(
items=(
# TODO: complain to buildingSMART
@@ -201,16 +201,18 @@ class BIMStairProperties(PropertyGroup):
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
def get_props_kwargs(self, convert_to_project_units=False):
def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
if not stair_type:
stair_type = self.stair_type
stair_kwargs = {
"stair_type": self.stair_type,
"stair_type": stair_type,
"width": self.width,
"height": self.height,
"number_of_treads": self.number_of_treads,
"tread_run": self.tread_run,
}
if self.stair_type == "CONCRETE":
if stair_type == "CONCRETE":
concrete_props = {
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
@@ -219,13 +221,13 @@ class BIMStairProperties(PropertyGroup):
}
stair_kwargs.update(concrete_props)
elif self.stair_type == "WOOD/STEEL":
elif stair_type == "WOOD/STEEL":
wood_steel_props = {
"tread_depth": self.tread_depth,
}
stair_kwargs.update(wood_steel_props)
elif self.stair_type == "GENERIC":
elif stair_type == "GENERIC":
pass
if not convert_to_project_units:
@@ -120,7 +120,7 @@ def update_railing_modifier_bmesh(context):
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
obj = context.object
obj = context.active_object
props = obj.BIMRailingProperties
# NOTE: using Data since bmesh update will hapen very often
@@ -274,9 +274,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a railing.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
if context.active_object is not None:
spawn_location = context.active_object.location.copy()
context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -348,8 +348,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRailingProperties
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
data["path_data"] = json.dumps(data["path_data"])
# required since we could load pset from .ifc and BIMRailingProperties won't be set
@@ -366,14 +365,13 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
props = obj.BIMRailingProperties
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
update_railing_modifier_bmesh(context)
props.is_editing = False
return {"FINISHED"}
@@ -438,11 +436,14 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRailingProperties
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
props.is_editing_path = True
update_railing_modifier_bmesh(context)
if bpy.context.object.mode != "EDIT":
if bpy.context.active_object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: cancel_editing_railing_path(context))
@@ -456,9 +457,24 @@ def cancel_editing_railing_path(context):
ProfileDecorator.uninstall()
props.is_editing_path = False
update_railing_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
if props.railing_type == "FRAMELESS_PANEL":
update_railing_modifier_bmesh(context)
else:
element = tool.Ifc.get_entity(obj)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=body,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
return {"FINISHED"}
@@ -492,7 +508,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
# since we know that BBIM_Railing could have changed
refresh()
update_railing_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
update_railing_modifier_ifc_data(context)
return {"FINISHED"}
@@ -424,7 +424,7 @@ def update_roof_modifier_bmesh(context):
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
obj = context.object
obj = context.active_object
props = obj.BIMRoofProperties
# NOTE: using Data since bmesh update will hapen very often
@@ -506,9 +506,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a roof.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
if context.active_object is not None:
spawn_location = context.active_object.location.copy()
context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -593,8 +593,7 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = True
@@ -608,8 +607,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
props = obj.BIMRoofProperties
# restore previous settings since editing was canceled
@@ -650,11 +648,14 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRoofProperties
data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
props.is_editing_path = True
update_roof_modifier_bmesh(context)
if bpy.context.object.mode != "EDIT":
if bpy.context.active_object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
@@ -706,7 +707,7 @@ def cancel_editing_roof_path(context):
props.is_editing_path = False
update_roof_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"}
@@ -747,7 +748,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
update_roof_modifier_bmesh(context)
update_roof_modifier_ifc_data(context)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
update_roof_modifier_ifc_data(context)
return {"FINISHED"}
@@ -789,7 +790,7 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
# tried to avoid bmesh with foreach_get and foreach_set
# but in EDIT mode it's only possible to change attributes by working with bmesh
me = context.object.data
me = context.active_object.data
bm = tool.Blender.get_bmesh_for_mesh(me)
# check if attribute exists or create one
@@ -215,7 +215,7 @@ def update_stair_modifier(context):
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
vertices, edges, faces = generate_stair_2d_profile(**props_kwargs)
obj = context.object
obj = context.active_object
bm = bmesh.new()
bm.verts.index_update()
bm.edges.index_update()
@@ -234,7 +234,7 @@ def update_stair_modifier(context):
translate_verts = [v for v in extruded["geom"] if isinstance(v, BMVert)]
bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts)
if context.object.mode == "EDIT":
if context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -316,9 +316,9 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a stair.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
if context.active_object is not None:
spawn_location = context.active_object.location.copy()
context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -51,7 +51,7 @@ def update_sverchok_modifier(context):
bm.edges.index_update()
bm.faces.index_update()
if context.object.mode == "EDIT":
if context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -41,7 +41,7 @@ from blenderbim.bim.helper import prop_with_search
class LaunchTypeManager(bpy.types.Operator):
bl_idname = "bim.launch_type_manager"
bl_label = "Launch Type Manager"
bl_options = {"REGISTER"}
bl_options = {"REGISTER", "UNDO"}
bl_description = "Display all available Construction Types to add new instances"
def execute(self, context):
@@ -127,6 +127,8 @@ class LaunchTypeManager(bpy.types.Operator):
op.ifc_class = relating_type["ifc_class"]
op.relating_type_id = relating_type["id"]
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
op.element = relating_type["id"]
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
op.relating_type = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
@@ -166,12 +168,13 @@ class BIM_PT_Grids(Panel):
class BIM_PT_array(bpy.types.Panel):
bl_label = "IFC Array"
bl_label = "Array"
bl_idname = "BIM_PT_array"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -234,12 +237,13 @@ class BIM_PT_array(bpy.types.Panel):
class BIM_PT_stair(bpy.types.Panel):
bl_label = "IFC Stair"
bl_label = "Stair"
bl_idname = "BIM_PT_stair"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -269,11 +273,9 @@ class BIM_PT_stair(bpy.types.Panel):
row.operator("bim.enable_editing_stair", icon="GREASEPENCIL", text="")
row.operator("bim.remove_stair", icon="X", text="")
row = self.layout.row(align=True)
for prop in props.get_props_kwargs():
prop_value = stair_data[prop]
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
for prop_name, prop_value in StairData.data["general_params"].items():
row = self.layout.row(align=True)
row.label(text=f"{props.bl_rna.properties[prop].name}")
row.label(text=prop_name)
row.label(text=str(prop_value))
# calculated properties
@@ -294,12 +296,13 @@ class BIM_PT_stair(bpy.types.Panel):
class BIM_PT_sverchok(bpy.types.Panel):
bl_label = "IFC Sverchok"
bl_label = "Sverchok"
bl_idname = "BIM_PT_sverchok"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -332,12 +335,13 @@ class BIM_PT_sverchok(bpy.types.Panel):
class BIM_PT_window(bpy.types.Panel):
bl_label = "IFC Window"
bl_label = "Window"
bl_idname = "BIM_PT_window"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -441,12 +445,13 @@ class BIM_PT_window(bpy.types.Panel):
class BIM_PT_door(bpy.types.Panel):
bl_label = "IFC Door"
bl_label = "Door"
bl_idname = "BIM_PT_door"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -514,12 +519,13 @@ class BIM_PT_door(bpy.types.Panel):
class BIM_PT_railing(bpy.types.Panel):
bl_label = "IFC Railing"
bl_label = "Railing"
bl_idname = "BIM_PT_railing"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -576,12 +582,13 @@ class BIM_PT_railing(bpy.types.Panel):
class BIM_PT_roof(bpy.types.Panel):
bl_label = "IFC Roof"
bl_label = "Roof"
bl_idname = "BIM_PT_roof"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -631,7 +638,7 @@ class BIM_PT_roof(bpy.types.Panel):
class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model"
bl_label = "IFC Objects"
bl_label = "Objects"
def draw(self, context):
layout = self.layout
@@ -60,11 +60,11 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
for obj in selected_objs:
joiner.unjoin(obj)
return {"FINISHED"}
if not context.active_object or not context.active_object.BIMObjectProperties.ifc_definition_id:
self.report({"ERROR"}, f"No active object selected")
return {"CANCELLED"}
for obj in selected_objs:
tool.Geometry.clear_scale(obj)
@@ -75,7 +75,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
if len(selected_objs) == 1:
joiner.join_E(context.active_object, context.scene.cursor.location)
return {"FINISHED"}
if self.join_type in ("L", "V"):
if len(selected_objs) != 2:
self.report({"ERROR"}, f"It requires 2 selected objects to do join of type {self.join_type}")
@@ -86,7 +86,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
elif self.join_type == "V":
joiner.join_V(another_selected_object, context.active_object)
return {"FINISHED"}
if self.join_type == "T":
elements = [tool.Ifc.get_entity(o) for o in context.selected_objects]
layer2_elements = []
@@ -560,7 +560,7 @@ class DumbWallGenerator:
rotation = math.atan2(normal[1], normal[0])
rotated_y_axis = Matrix.Rotation(-rotation, 4, "Z")[1].xyz
# since wall thickness goes by local Y+ axis
# we find best position for the next wall
# by finding the face of another wall that will be very close to the some test point.
@@ -1137,7 +1137,7 @@ class DumbWallJoiner:
previous_matrix = obj.matrix_world.copy()
previous_origin = previous_matrix.col[3].to_2d()
obj.matrix_world[0][3], obj.matrix_world[1][3] = self.body[0]
obj.matrix_world.col[3].xy = self.body[0]
bpy.context.view_layer.update()
for rel in element.ConnectedFrom:
@@ -1432,6 +1432,7 @@ class DumbWallJoiner:
return True
def clip(self, wall1, slab2):
"""returns height of the clipped wall, adds clipping plane to `clippings`"""
element1 = tool.Ifc.get_entity(wall1)
element2 = tool.Ifc.get_entity(slab2)
@@ -1441,19 +1442,15 @@ class DumbWallJoiner:
bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()]
extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id))
d = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
slab_pt = slab2.matrix_world @ Vector((0, 0, 0))
slab_dir = slab2.matrix_world.to_quaternion() @ Vector((0, 0, -1))
tops = [mathutils.geometry.intersect_line_plane(b, b + d, slab_pt, slab_dir) for b in bases]
i_bottom = None
i_top = None
for i, co in enumerate(tops):
if i_top is None or co[2] > i_top[2]:
i_top = co
i_bottom = bases[i]
tops = [mathutils.geometry.intersect_line_plane(b, b + wall_dir, slab_pt, slab_dir) for b in bases]
top_index = max(range(4), key=lambda i: tops[i].z)
i_top = tops[top_index]
i_bottom = bases[top_index]
quaternion = slab2.matrix_world.to_quaternion()
x_axis = quaternion @ Vector((1, 0, 0))
@@ -292,7 +292,7 @@ def create_bm_window(
def update_window_modifier_bmesh(context):
obj = context.object
obj = context.active_object
props = obj.BIMWindowProperties
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
accumulated_height = [0] * len(panel_schema[0])
@@ -425,7 +425,7 @@ def update_window_modifier_bmesh(context):
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=bm.verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -444,9 +444,9 @@ class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a window.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
if context.active_object is not None:
spawn_location = context.active_object.location.copy()
context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -176,7 +176,11 @@ class BimToolUI:
row.operator("bim.join_wall", icon="X", text="").join_type = ""
elif AuthoringData.data["active_material_usage"] == "LAYER3":
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
if len(context.selected_objects) == 1:
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
elif "LAYER2" in AuthoringData.data["selected_material_usages"]:
add_layout_hotkey_operator(cls.layout, "Extend Wall To Slab", "S_E", "")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="x_angle", text="X Angle")
@@ -457,7 +461,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if len(bpy.context.selected_objects) == 1:
if self.active_material_usage == "LAYER3":
# Edit LAYER2 profile
# Edit LAYER3 profile
if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT":
bpy.ops.bim.enable_editing_extrusion_profile()
elif self.active_material_usage == "LAYER2":
@@ -469,6 +473,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
else:
# Edit SWEPTSOLID profile (assuming single profile for now)
bpy.ops.bim.enable_editing_extrusion_profile()
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
# Extend PROFILEs to LAYER2
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
@@ -77,7 +77,7 @@ def draw_addresses(box, parent):
class BIM_PT_people(bpy.types.Panel):
bl_label = "IFC People"
bl_label = "People"
bl_idname = "BIM_PT_people"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -136,7 +136,7 @@ class BIM_PT_people(bpy.types.Panel):
class BIM_PT_organisations(bpy.types.Panel):
bl_label = "IFC Organisations"
bl_label = "Organisations"
bl_idname = "BIM_PT_organisations"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -183,7 +183,7 @@ class BIM_PT_organisations(bpy.types.Panel):
class BIM_PT_owner(bpy.types.Panel):
bl_label = "IFC Owner History"
bl_label = "Owner History"
bl_idname = "BIM_PT_owner"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -234,7 +234,7 @@ class BIM_PT_owner(bpy.types.Panel):
class BIM_PT_actor(bpy.types.Panel):
bl_label = "IFC Actor"
bl_label = "Actor"
bl_idname = "BIM_PT_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -284,14 +284,14 @@ class BIM_PT_actor(bpy.types.Panel):
class BIM_PT_object_actor(bpy.types.Panel):
bl_label = "IFC Actor"
bl_label = "Actor"
bl_idname = "BIM_PT_object_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -23,13 +23,13 @@ from blenderbim.bim.helper import draw_attributes
class BIM_PT_patch(bpy.types.Panel):
bl_label = "IFC Patch"
bl_label = "Patch"
bl_idname = "BIM_PT_patch"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
@@ -25,7 +25,7 @@ from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_pro
class BIM_PT_profiles(Panel):
bl_label = "IFC Profiles"
bl_label = "Profiles"
bl_idname = "BIM_PT_profiles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -582,34 +582,34 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
@persistent
def load_handler(*args):
bpy.app.handlers.load_post.remove(load_handler)
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
self.finish_loading_project(context)
if self.should_start_fresh_session:
# WARNING: wm.read_homefile clears context
# which could lead to some operators to fail:
# WARNING: wm.read_homefile clears context which could lead to some
# operators to fail:
# https://blender.stackexchange.com/a/282558/135166
# So we continue using the load_post handler
# thats triggered when context is already restored
# So we continue using the load_post handler thats triggered when
# context is already restored
bpy.app.handlers.load_post.append(load_handler)
bpy.ops.wm.read_homefile()
return {"FINISHED"}
else:
return self.finish_loading_project(context)
def finish_loading_project(self, context):
if not self.is_existing_ifc_file():
return {"FINISHED"}
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
context.scene.BIMProperties.ifc_file = self.get_filepath()
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
if not self.is_advanced:
bpy.ops.bim.load_project_elements()
return {"FINISHED"}
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
@@ -987,7 +987,7 @@ class ExportIFC(bpy.types.Operator):
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
blenderbim.bim.handler.purge_module_data()
self.report({"INFO"}, f"IFC Project \"{os.path.basename(output_file)}\" Saved")
self.report({"INFO"}, f'IFC Project "{os.path.basename(output_file)}" Saved')
if bpy.data.is_saved:
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
@@ -41,6 +41,12 @@ class BIM_MT_new_project(Menu):
bl_label = "New Project"
def draw(self, context):
self.layout.operator_context = "INVOKE_DEFAULT"
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
# Do we need to set it back to exec default?
# self.layout.operator_context = "EXEC_DEFAULT"
self.layout.separator()
self.layout.label(text="New IFC Project", icon_value=blenderbim.bim.icons["IFC"].icon_id)
self.layout.operator("bim.new_project", text="Metric (m) Project").preset = "metric_m"
self.layout.operator("bim.new_project", text="Metric (mm) Project").preset = "metric_mm"
@@ -70,7 +76,7 @@ def file_menu(self, context):
class BIM_PT_project(Panel):
bl_label = "IFC Project"
bl_label = "Current Project"
bl_idname = "BIM_PT_project"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -209,7 +215,6 @@ class BIM_PT_project(Panel):
row = self.layout.row()
row.label(text="File Not Saved", icon="ERROR")
def draw_create_project_ui(self, context):
props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties
@@ -226,11 +231,11 @@ class BIM_PT_project(Panel):
row = self.layout.row(align=True)
row.operator("bim.create_project")
row.operator("bim.load_project").should_start_fresh_session = True
row.operator("bim.load_project").should_start_fresh_session = False
class BIM_PT_project_library(Panel):
bl_label = "IFC Project Library"
bl_label = "Project Library"
bl_idname = "BIM_PT_project_library"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -272,7 +277,7 @@ class BIM_PT_project_library(Panel):
class BIM_PT_links(Panel):
bl_label = "IFC Links"
bl_label = "Links"
bl_idname = "BIM_PT_links"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
+14 -14
View File
@@ -170,12 +170,12 @@ def draw_psetqto_editable_ui(box, props, prop):
class BIM_PT_object_psets(Panel):
bl_label = "IFC Object Property Sets"
bl_label = "Object Property Sets"
bl_idname = "BIM_PT_object_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -212,12 +212,12 @@ class BIM_PT_object_psets(Panel):
class BIM_PT_object_qtos(Panel):
bl_label = "IFC Object Quantity Sets"
bl_label = "Object Quantity Sets"
bl_idname = "BIM_PT_object_qtos"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -249,7 +249,7 @@ class BIM_PT_object_qtos(Panel):
class BIM_PT_material_psets(Panel):
bl_label = "IFC Material Property Sets"
bl_label = "Material Property Sets"
bl_idname = "BIM_PT_material_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -293,7 +293,7 @@ class BIM_PT_material_psets(Panel):
class BIM_PT_material_set_psets(Panel):
bl_label = "IFC Material Set Property Sets"
bl_label = "Material Set Property Sets"
bl_idname = "BIM_PT_material_set_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -330,7 +330,7 @@ class BIM_PT_material_set_psets(Panel):
class BIM_PT_material_set_item_psets(Panel):
bl_label = "IFC Material Set Item Property Sets"
bl_label = "Material Set Item Property Sets"
bl_idname = "BIM_PT_material_set_item_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -367,7 +367,7 @@ class BIM_PT_material_set_item_psets(Panel):
class BIM_PT_task_qtos(Panel):
bl_label = "IFC Task Quantity Sets"
bl_label = "Task Quantity Sets"
bl_idname = "BIM_PT_task_qtos"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -403,7 +403,7 @@ class BIM_PT_task_qtos(Panel):
class BIM_PT_resource_qtos(Panel):
bl_label = "IFC Resource Quantity Sets"
bl_label = "Resource Quantity Sets"
bl_idname = "BIM_PT_resource_qtos"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -437,7 +437,7 @@ class BIM_PT_resource_qtos(Panel):
class BIM_PT_resource_psets(Panel):
bl_label = "IFC Resource Property Sets"
bl_label = "Resource Property Sets"
bl_idname = "BIM_PT_resource_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -471,7 +471,7 @@ class BIM_PT_resource_psets(Panel):
class BIM_PT_profile_psets(Panel):
bl_label = "IFC Profile Property Sets"
bl_label = "Profile Property Sets"
bl_idname = "BIM_PT_profile_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -507,7 +507,7 @@ class BIM_PT_profile_psets(Panel):
class BIM_PT_work_schedule_psets(Panel):
bl_label = "IFC Work Schedule Property Sets"
bl_label = "Work Schedule Property Sets"
bl_idname = "BIM_PT_work_schedule_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -539,12 +539,12 @@ class BIM_PT_work_schedule_psets(Panel):
class BIM_PT_bulk_property_editor(Panel):
bl_label = "IFC Bulk Property Editor"
bl_label = "Bulk Property Editor"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_utilities_object"
bl_parent_id = "BIM_PT_tab_misc"
def draw(self, context):
pass
@@ -23,7 +23,7 @@ from blenderbim.bim.module.pset_template.data import PsetTemplatesData
class BIM_PT_pset_template(Panel):
bl_label = "IFC Property Set Templates"
bl_label = "Property Set Templates"
bl_idname = "BIM_PT_pset_template"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -23,13 +23,13 @@ from blenderbim.bim.module.resource.data import ResourceData
class BIM_PT_resources(Panel):
bl_label = "IFC Resources"
bl_label = "Resources"
bl_idname = "BIM_PT_resources"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell.util.element
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
import blenderbim.tool as tool
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
@@ -70,7 +71,7 @@ class IfcClassData:
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
return [(e, e, (get_entity_doc(version, e) or {}).get("description", "")) for e in products]
return [(e, blenderbim.bim.helper.uncamel(e), (get_entity_doc(version, e) or {}).get("description", "")) for e in products]
@classmethod
def ifc_classes(cls):
@@ -86,7 +87,7 @@ class IfcClassData:
# Yeah, weird isn't it.
names.remove("IfcOpeningStandardCase")
version = tool.Ifc.get_schema()
return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)]
return [(c, blenderbim.bim.helper.uncamel(c), (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)]
@classmethod
def ifc_predefined_types(cls):
@@ -163,9 +164,10 @@ class IfcClassData:
if not element:
return
name = element.is_a()
name = blenderbim.bim.helper.uncamel(name)
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
if predefined_type:
name += f"[{predefined_type}]"
name += f" [{predefined_type}]"
return name
@classmethod
@@ -25,12 +25,13 @@ from blenderbim.bim.module.root.data import IfcClassData
class BIM_PT_class(Panel):
bl_label = "IFC Class"
bl_label = "Class"
bl_idname = "BIM_PT_class"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -22,7 +22,7 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_search(Panel):
bl_label = "IFC Search"
bl_label = "Search"
bl_idname = "BIM_PT_search"
# bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -109,7 +109,7 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList):
class BIM_PT_IFCSelector(Panel):
bl_label = "IFC Selector"
bl_label = "Selector"
bl_idname = "BIM_PT_ifc_selector"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -25,13 +25,13 @@ from blenderbim.bim.module.sequence.data import WorkPlansData, WorkScheduleData,
class BIM_PT_work_plans(Panel):
bl_label = "IFC Work Plans"
bl_label = "Work Plans"
bl_idname = "BIM_PT_work_plans"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -97,13 +97,13 @@ class BIM_PT_work_plans(Panel):
class BIM_PT_work_schedules(Panel):
bl_label = "IFC Work Schedules"
bl_label = "Work Schedules"
bl_idname = "BIM_PT_work_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -526,7 +526,7 @@ class BIM_PT_work_schedules(Panel):
class BIM_PT_task_icom(Panel):
bl_label = "IFC Task ICOM"
bl_label = "Task ICOM"
bl_idname = "BIM_PT_task_icom"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -811,13 +811,13 @@ class BIM_UL_tasks(UIList):
class BIM_PT_work_calendars(Panel):
bl_label = "IFC Work Calendars"
bl_label = "Work Calendars"
bl_idname = "BIM_PT_work_calendars"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -43,7 +43,11 @@ class SpatialData:
@classmethod
def containers(cls):
results = {}
for container in tool.Ifc.get().by_type("IfcSpatialElement"):
if tool.Ifc.get_schema() == "IFC2X3":
spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
else:
spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement")
for container in spatial_elements:
results[container.id()] = {
"type": container.is_a(),
"id": container.id(),
@@ -23,12 +23,12 @@ import blenderbim.tool as tool
class BIM_PT_spatial(Panel):
bl_label = "IFC Spatial Container"
bl_label = "Spatial Container"
bl_idname = "BIM_PT_spatial"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -72,7 +72,7 @@ class BIM_PT_spatial(Panel):
if SpatialData.data["is_directly_contained"]:
row.operator("bim.remove_container", icon="X", text="")
else:
row.label(text="This object is not spatially contained")
row.label(text="No Spatial Container")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
for reference in SpatialData.data["references"]:
row = self.layout.row()
@@ -97,7 +97,7 @@ class BIM_UL_containers(UIList):
class BIM_PT_SpatialManager(Panel):
bl_label = "IFC Spatial Manager"
bl_label = "Spatial Manager"
bl_idname = "BIM_PT_SpatialManager"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -86,14 +86,14 @@ def draw_boundary_condition_read_only_ui(layout, boundary_condition):
class BIM_PT_structural_boundary_conditions(Panel):
bl_label = "IFC Structural Boundary Conditions"
bl_label = "Structural Boundary Conditions"
bl_idname = "BIM_PT_structural_boundary_conditions"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -121,14 +121,14 @@ class BIM_PT_structural_boundary_conditions(Panel):
class BIM_PT_connected_structural_members(Panel):
bl_label = "IFC Connected Structural Members"
bl_label = "Connected Structural Members"
bl_idname = "BIM_PT_connected_structural_members"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -174,14 +174,14 @@ class BIM_PT_connected_structural_members(Panel):
class BIM_PT_structural_member(Panel):
bl_label = "IFC Structural Member"
bl_label = "Structural Member"
bl_idname = "BIM_PT_structural_member"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -217,14 +217,14 @@ class BIM_PT_structural_member(Panel):
class BIM_PT_structural_connection(Panel):
bl_label = "IFC Structural Connection"
bl_label = "Structural Connection"
bl_idname = "BIM_PT_structural_connection"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -277,13 +277,13 @@ class BIM_PT_structural_connection(Panel):
class BIM_PT_structural_analysis_models(Panel):
bl_label = "IFC Structural Analysis Models"
bl_label = "Structural Analysis Models"
bl_idname = "BIM_PT_structural_analysis_models"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_structural"
bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -352,13 +352,13 @@ class BIM_UL_structural_analysis_models(UIList):
class BIM_PT_structural_load_cases(Panel):
bl_label = "IFC Structural Load Cases"
bl_label = "Structural Load Cases"
bl_idname = "BIM_PT_structural_load_cases"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_structural"
bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -448,13 +448,13 @@ class BIM_UL_structural_activities(UIList):
class BIM_PT_structural_loads(Panel):
bl_label = "IFC Structural Loads"
bl_label = "Structural Loads"
bl_idname = "BIM_PT_structural_loads"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_structural"
bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -518,13 +518,13 @@ class BIM_UL_structural_loads(UIList):
class BIM_PT_boundary_conditions(Panel):
bl_label = "IFC Boundary Conditions"
bl_label = "Boundary Conditions"
bl_idname = "BIM_PT_boundary_conditions"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_structural"
bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -26,7 +26,7 @@ from blenderbim.tool.style import TEXTURE_MAPS_BY_METHODS, STYLE_TEXTURE_PROPS_M
class BIM_PT_styles(Panel):
bl_label = "IFC Styles"
bl_label = "Styles"
bl_idname = "BIM_PT_styles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -86,7 +86,7 @@ def draw_style_ui(self, context):
class BIM_PT_style(MaterialButtonsPanel, Panel):
bl_label = "IFC Style"
bl_label = "Style"
bl_idname = "BIM_PT_style"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -111,7 +111,7 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
class BIM_PT_style_attributes(Panel):
bl_label = "IFC Style Attributes"
bl_label = "Style Attributes"
bl_idname = "BIM_PT_style_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -159,7 +159,7 @@ class BIM_PT_style_attributes(Panel):
class BIM_PT_external_style_attributes(Panel):
bl_label = "IFC External Surface Style"
bl_label = "External Surface Style"
bl_idname = "BIM_PT_external_style_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -226,7 +226,7 @@ class BIM_UL_styles(UIList):
class BIM_PT_STYLE_GRAPH(Panel):
bl_idname = "BIM_PT_style_graph"
bl_space_type = "NODE_EDITOR"
bl_label = "IFC Style Graph Settings"
bl_label = "Style Graph Settings"
bl_region_type = "UI"
bl_category = "BBIM"
@@ -24,13 +24,13 @@ from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, Port
class BIM_PT_systems(Panel):
bl_label = "IFC Systems"
bl_label = "Systems"
bl_idname = "BIM_PT_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_services"
bl_parent_id = "BIM_PT_tab_services"
@classmethod
def poll(cls, context):
@@ -74,14 +74,14 @@ class BIM_PT_systems(Panel):
class BIM_PT_object_systems(Panel):
bl_label = "IFC Systems"
bl_label = "Systems"
bl_idname = "BIM_PT_object_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_services_object"
bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
@@ -130,14 +130,14 @@ class BIM_PT_object_systems(Panel):
class BIM_PT_ports(Panel):
bl_label = "IFC Ports"
bl_label = "Ports"
bl_idname = "BIM_PT_ports"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_services_object"
bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
@@ -163,14 +163,14 @@ class BIM_PT_ports(Panel):
class BIM_PT_port(Panel):
bl_label = "IFC Port"
bl_label = "Port"
bl_idname = "BIM_PT_port"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_services_object"
bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
@@ -28,7 +28,7 @@ class BIM_PT_tester(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
self.layout.use_property_split = True
@@ -27,6 +27,7 @@ classes = (
operator.EnableEditingType,
operator.PurgeUnusedTypes,
operator.RemoveType,
operator.RenameType,
operator.SelectSimilarType,
operator.SelectType,
operator.SelectTypeObjects,
@@ -56,6 +56,8 @@ class TypeData:
return []
version = tool.Ifc.get_schema()
types = ifcopenshell.util.type.get_applicable_types(element.is_a(), schema=version)
if element.is_a("IfcAnnotation"):
types.append("IfcTypeProduct")
results.extend((t, t, get_entity_doc(version, t).get("description", "")) for t in types)
return results
@@ -436,7 +436,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_type"
bl_label = "Remove Type"
bl_options = {"REGISTER"}
bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty()
def _execute(self, context):
@@ -446,13 +446,35 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
if obj:
tool.Ifc.unlink(obj=obj)
bpy.data.objects.remove(obj)
return {"FINISHED"}
class RenameType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.rename_type"
bl_label = "Rename Type"
bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty()
name: bpy.props.StringProperty(name="Name")
def _execute(self, context):
element = tool.Ifc.get().by_id(self.element)
obj = tool.Ifc.get_object(element)
element.Name = self.name
if obj:
tool.Root.set_object_name(obj, element)
def invoke(self, context, event):
element = tool.Ifc.get().by_id(self.element)
self.name = element.Name or "Unnamed"
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
self.layout.prop(self, "name")
class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_type"
bl_label = "Duplicate Type"
bl_options = {"REGISTER"}
bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty()
def _execute(self, context):
@@ -478,7 +500,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
class PurgeUnusedTypes(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.purge_unused_types"
bl_label = "Purge Unused Types"
bl_options = {"REGISTER"}
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.purge_unused_types(tool.Ifc, tool.Type)
@@ -25,12 +25,12 @@ from blenderbim.bim.module.type.data import TypeData
class BIM_PT_type(Panel):
bl_label = "IFC Type"
bl_label = "Type"
bl_idname = "BIM_PT_type"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -92,7 +92,7 @@ class BIM_PT_type(Panel):
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
row.operator("bim.unassign_type", icon="X", text="")
else:
row.label(text="This object has no type")
row.label(text="No Relating Type")
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
@@ -24,7 +24,7 @@ from blenderbim.bim.module.unit.data import UnitsData
class BIM_PT_units(Panel):
bl_label = "IFC Units"
bl_label = "Units"
bl_idname = "BIM_PT_units"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -23,14 +23,14 @@ from blenderbim.bim.module.void.data import BooleansData, VoidsData
class BIM_PT_voids(Panel):
bl_label = "IFC Voids"
bl_label = "Voids"
bl_idname = "BIM_PT_voids"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -79,7 +79,7 @@ class BIM_PT_voids(Panel):
class BIM_PT_booleans(Panel):
bl_label = "IFC Booleans"
bl_label = "Booleans"
bl_idname = "BIM_PT_booleans"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -109,8 +109,10 @@ class BIM_PT_booleans(Panel):
if context.active_object.data.BIMMeshProperties.ifc_definition_id:
row = layout.row(align=True)
row.label(text=f"{BooleansData.data['total_booleans']} Booleans Found")
row.operator("bim.add_boolean", text="", icon="ADD")
row.operator("bim.show_booleans", text="", icon="HIDE_OFF")
row.operator("bim.add_boolean", text="Apply Boolean", icon="ADD")
show_boolean_button = row.row(align=True)
show_boolean_button.operator("bim.show_booleans", text="", icon="HIDE_OFF")
show_boolean_button.enabled = BooleansData.data['total_booleans'] > 0
row.operator("bim.hide_booleans", text="", icon="HIDE_ON")
elif context.active_object.data.BIMMeshProperties.ifc_boolean_id:
row = layout.row()
+15
View File
@@ -40,6 +40,21 @@ from mathutils import Vector, Matrix, Euler
from math import radians
class SetTab(bpy.types.Operator):
bl_idname = "bim.set_tab"
bl_label = "Set Tab"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Sets the current property tab"
tab: bpy.props.StringProperty()
def execute(self, context):
if context.area.spaces.active.search_filter:
return {"FINISHED"}
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
aprops.tab = self.tab
return {"FINISHED"}
class SwitchTab(bpy.types.Operator):
bl_idname = "bim.switch_tab"
bl_label = "Switch Tab"
+5 -3
View File
@@ -345,13 +345,13 @@ def get_tab(self, context):
return [
("PROJECT", "Project Overview", "", blenderbim.bim.icons["IFC"].icon_id, 0),
("OBJECT", "Object Information", "", "FILE_3D", 1),
("MATERIALS", "Materials and Styles", "", "MATERIAL", 2),
("GEOMETRY", "Geometry and Materials", "", "MATERIAL", 2),
("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 3),
("SERVICES", "Services and Systems", "", "NETWORK_DRIVE", 4),
("STRUCTURE", "Structural Analysis", "", "EDITMODE_HLT", 5),
("SCHEDULING", "Construction Scheduling", "", "NLA", 6),
("FM", "Facility Management", "", "PACKAGE", 7),
("OTHER", "Other Utilities", "", "COLLAPSEMENU", 8),
("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8),
("BLENDER", "Blender Properties", "", "BLENDER", 9),
]
@@ -359,7 +359,9 @@ def get_tab(self, context):
class BIMAreaProperties(PropertyGroup):
tab: EnumProperty(default=0, items=get_tab, name="Tab", update=update_tab)
previous_tab: StringProperty(default="PROJECT", name="Previous Tab")
alt_tab: StringProperty(default="PROJECT", name="Alt Tab")
alt_tab: StringProperty(default="OBJECT", name="Alt Tab")
active_tab: BoolProperty(default=True, name="Active Tab")
inactive_tab: BoolProperty(default=False, name="Inactive Tab")
class BIMProperties(PropertyGroup):
+152 -47
View File
@@ -270,7 +270,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
# Scene panel groups
class BIM_PT_root(Panel):
class BIM_PT_tabs(Panel):
bl_label = "BlenderBIM Add-on"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -281,15 +281,52 @@ class BIM_PT_root(Panel):
def draw(self, context):
try:
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
row = self.layout.row()
row.operator(
"bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id
).tab = "PROJECT"
row.operator("bim.set_tab", text="", emboss=False, icon="FILE_3D").tab = "OBJECT"
row.operator("bim.set_tab", text="", emboss=False, icon="MATERIAL").tab = "GEOMETRY"
row.operator("bim.set_tab", text="", emboss=False, icon="DOCUMENTS").tab = "DRAWINGS"
row.operator("bim.set_tab", text="", emboss=False, icon="NETWORK_DRIVE").tab = "SERVICES"
row.operator("bim.set_tab", text="", emboss=False, icon="EDITMODE_HLT").tab = "STRUCTURE"
row.operator("bim.set_tab", text="", emboss=False, icon="NLA").tab = "SCHEDULING"
row.operator("bim.set_tab", text="", emboss=False, icon="PACKAGE").tab = "FM"
row.operator("bim.set_tab", text="", emboss=False, icon="COMMUNITY").tab = "QUALITY"
row.operator("bim.set_tab", text="", emboss=False, icon="BLENDER").tab = "BLENDER"
row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT")
# Yes, that's right.
row = self.layout.row()
row.scale_y = 0.2
for tab in [
"PROJECT",
"OBJECT",
"GEOMETRY",
"DRAWINGS",
"SERVICES",
"STRUCTURE",
"SCHEDULING",
"FM",
"QUALITY",
"BLENDER",
"SWITCH",
]:
if aprops.tab == tab:
row.prop(aprops, "active_tab", text="", icon="BLANK1")
else:
row.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False)
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
row = self.layout.row(align=True)
row.prop(aprops, "tab", text="")
row.operator("bim.switch_tab", text="", icon="UV_SYNC_SELECT")
except:
pass # Prior to load_post, we may not have any area properties setup
class BIM_PT_project_info(Panel):
bl_label = "IFC Project Info"
bl_label = "Project Info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -303,7 +340,7 @@ class BIM_PT_project_info(Panel):
class BIM_PT_project_setup(Panel):
bl_label = "IFC Project Setup"
bl_label = "Project Setup"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -317,23 +354,22 @@ class BIM_PT_project_setup(Panel):
pass
class BIM_PT_collaboration(Panel):
bl_label = "IFC Collaboration"
class BIM_PT_tab_collaboration(Panel):
bl_label = "Collaboration"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "OTHER")
return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
class BIM_PT_selection(Panel):
bl_label = "IFC Selection"
bl_label = "Selection"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -348,11 +384,10 @@ class BIM_PT_selection(Panel):
class BIM_PT_geometry(Panel):
bl_label = "IFC Geometry"
bl_label = "Geometry"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -362,12 +397,11 @@ class BIM_PT_geometry(Panel):
pass
class BIM_PT_4D5D(Panel):
bl_label = "IFC Costing and Scheduling"
class BIM_PT_tab_4D5D(Panel):
bl_label = "Costing and Scheduling"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -377,12 +411,11 @@ class BIM_PT_4D5D(Panel):
pass
class BIM_PT_structural(Panel):
bl_label = "IFC Structural"
class BIM_PT_tab_structural(Panel):
bl_label = "Structural"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -392,12 +425,11 @@ class BIM_PT_structural(Panel):
pass
class BIM_PT_services(Panel):
bl_label = "IFC Services"
class BIM_PT_tab_services(Panel):
bl_label = "Services"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -407,22 +439,21 @@ class BIM_PT_services(Panel):
pass
class BIM_PT_quality_control(Panel):
bl_label = "IFC Quality Control"
class BIM_PT_tab_quality_control(Panel):
bl_label = "Quality Control"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "OTHER")
return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
class BIM_PT_integrations(Panel):
class BIM_PT_tab_integrations(Panel):
bl_label = "BIM Integrations"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -431,87 +462,161 @@ class BIM_PT_integrations(Panel):
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "OTHER")
return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
# Object panel groups
class BIM_PT_object_metadata(Panel):
bl_label = "IFC Object Metadata"
class BIM_PT_tab_object_metadata(Panel):
bl_label = "Object Metadata"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_geometry_object(Panel):
bl_label = "IFC Geometry"
class BIM_PT_tab_representations(Panel):
bl_label = "Representations"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_services_object(Panel):
bl_label = "IFC Services"
class BIM_PT_tab_geometric_relationships(Panel):
bl_label = "Geometric Relationships"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_utilities_object(Panel):
bl_label = "IFC Utilities"
class BIM_PT_tab_parametric_geometry(Panel):
bl_label = "Parametric Geometry"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_misc_object(Panel):
bl_label = "IFC Misc."
class BIM_PT_tab_materials(Panel):
bl_label = "Materials"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_styles(Panel):
bl_label = "Styles"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_services_object(Panel):
bl_label = "Services"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SERVICES") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_misc(Panel):
bl_label = "Misc."
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_handover(Panel):
bl_label = "Commissioning and Handover"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_operations(Panel):
bl_label = "Operations and Maintenance"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 2
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
def draw(self, context):
pass
@@ -339,6 +339,9 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
if drawing_tool.does_file_exist(uri):
drawing_tool.update_embedded_svg_location(uri, old_location, new_location)
if drawing_tool.is_editing_sheets():
drawing_tool.import_sheets()
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
context = drawing_tool.get_annotation_context(
+9 -1
View File
@@ -88,7 +88,15 @@ def switch_representation(
should_sync_changes_first=False,
apply_openings=True,
):
"""Function can switch to representation that wasn't yet assigned to that object. See #2766."""
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
`should_reload` - reload `obj.data` from ifc representation;
`is_global` - replace mesh data for all users of `obj.data`, not just `obj`;
"""
if should_sync_changes_first and geometry.is_edited(obj) and not geometry.is_box_representation(representation):
representation_id = geometry.get_representation_id(representation)
geometry.run_geometry_update_representation(obj=obj)
+4 -3
View File
@@ -252,16 +252,16 @@ class Drawing:
def delete_object(cls, obj): pass
def disable_editing_assigned_product(cls, obj): pass
def disable_editing_drawings(cls): pass
def disable_editing_schedules(cls): pass
def disable_editing_references(cls): pass
def disable_editing_schedules(cls): pass
def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass
def does_file_exist(cls, uri): pass
def enable_editing(cls, obj): pass
def enable_editing_assigned_product(cls, obj): pass
def enable_editing_drawings(cls): pass
def enable_editing_schedules(cls): pass
def enable_editing_references(cls): pass
def enable_editing_schedules(cls): pass
def enable_editing_sheets(cls): pass
def enable_editing_text(cls, obj): pass
def ensure_unique_drawing_name(cls, name): pass
@@ -297,13 +297,14 @@ class Drawing:
def get_text_literal(cls, obj): pass
def get_unit_system(cls): pass
def import_assigned_product(cls, obj): pass
def import_drawings(cls): pass
def import_documents(cls, document_type): pass
def import_drawings(cls): pass
def import_sheets(cls): pass
def import_text_attributes(cls, obj): pass
def is_active_drawing(cls, drawing): pass
def is_camera_orthographic(cls): pass
def is_drawing_active(cls): pass
def is_editing_sheets(cls): pass
def move_file(cls, src, dest): pass
def open_spreadsheet(cls, uri): pass
def open_svg(cls, filepath): pass
+31 -2
View File
@@ -398,12 +398,12 @@ class Blender:
)
bmesh.update_edit_mesh(mesh)
if not obj:
if not bpy.context.object or bpy.context.object.data != mesh:
if not bpy.context.active_object or bpy.context.active_object.data != mesh:
raise Exception(
"Error applying bmesh in EDIT object - object is "
"not provided and can't be acquired from the context. "
)
obj = bpy.context.object
obj = bpy.context.active_object
obj.update_from_editmode()
else:
bm.to_mesh(mesh)
@@ -456,3 +456,32 @@ class Blender:
return bpy.ops.object.mode_set(mode="EDIT_GPENCIL", toggle=True)
else:
return {"CANCELLED"}
class Modifier:
@classmethod
def is_railing(cls, element):
return tool.Pset.get_element_pset(element, "BBIM_Railing")
@classmethod
def is_roof(cls, element):
return tool.Pset.get_element_pset(element, "BBIM_Roof")
@classmethod
def is_window(cls, element):
return tool.Pset.get_element_pset(element, "BBIM_Window")
@classmethod
def is_door(cls, element):
return tool.Pset.get_element_pset(element, "BBIM_Door")
@classmethod
def is_stair(cls, element):
return tool.Pset.get_element_pset(element, "BBIM_Stair")
@classmethod
def is_editing_parameters(cls, obj):
return obj.BIMRailingProperties.is_editing or obj.BIMRoofProperties.is_editing
@classmethod
def is_modifier_with_non_editable_path(cls, element):
return cls.is_stair(element) or cls.is_door(element) or cls.is_window(element)
+32
View File
@@ -24,6 +24,8 @@ import lark
import bmesh
import shutil
import logging
import shapely
from shapely.ops import unary_union
import mathutils
import webbrowser
import subprocess
@@ -97,6 +99,8 @@ class Drawing(blenderbim.core.tool.Drawing):
current_pos = camera.matrix_world @ current_pos
obj.location = current_pos
ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA")
@classmethod
def setup_annotation_object(cls, obj, object_type, related_object=None):
"""Finish object's adjustments after both object and entity are created"""
@@ -135,6 +139,30 @@ class Drawing(blenderbim.core.tool.Drawing):
cls.ensure_annotation_in_drawing_plane(obj)
assign_product = True
elif object_type == "REVISION_CLOUD":
revised_object, cloud = related_object, obj
verts = [np.array(revised_object.matrix_world @ v.co) for v in revised_object.data.vertices]
verts = [(np.around(v[[0, 1]], decimals=3)).tolist() for v in verts]
edges = [e.vertices for e in revised_object.data.edges]
# shapely magic
boundary_lines = [shapely.LineString([verts[v] for v in e]) for e in edges]
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
all_polygons = shapely.polygonize(unioned_boundaries.geoms).geoms
outer_shell = unary_union(all_polygons)
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
new_verts = list(outer_shell.exterior.coords)
bm_verts = [bm.verts.new(v + (0,)) for v in new_verts]
bm_edges = [bm.edges.new([bm_verts[i], bm_verts[i + 1]]) for i in range(len(new_verts) - 1)]
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
tool.Blender.apply_bmesh(obj.data, bm, obj)
cloud.location = Vector((0, 0, 0))
cls.ensure_annotation_in_drawing_plane(cloud)
assign_product = True
if assign_product and not cls.get_assigned_product(obj_entity):
tool.Ifc.run("drawing.assign_product", relating_product=related_entity, related_object=obj_entity)
@@ -466,6 +494,10 @@ class Drawing(blenderbim.core.tool.Drawing):
return items
return items[0]
@classmethod
def is_editing_sheets(cls):
return bpy.context.scene.DocProperties.is_editing_sheets
@classmethod
def remove_literal_from_annotation(cls, obj, literal):
element = tool.Ifc.get_entity(obj)
+4 -1
View File
@@ -35,6 +35,8 @@ class Geometry(blenderbim.core.tool.Geometry):
@classmethod
def change_object_data(cls, obj, data, is_global=False):
if is_global:
if obj.mode == "EDIT":
raise Exception("user_remap is not supported in EDIT mode")
obj.data.user_remap(data)
else:
obj.data = data
@@ -75,6 +77,7 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element)
return bpy.data.objects.remove(obj)
collection = obj.BIMObjectProperties.collection
if collection:
parent = ifcopenshell.util.element.get_aggregate(element)
@@ -98,7 +101,7 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.VoidsElements:
bpy.ops.bim.remove_opening(opening_id=element.id())
else:
is_spatial = element.is_a("IfcSpatialElement")
is_spatial = element.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement")
if getattr(element, "HasOpenings", None):
for rel in element.HasOpenings:
bpy.ops.bim.remove_opening(opening_id=rel.RelatedOpeningElement.id())
+50
View File
@@ -21,6 +21,8 @@ import bpy
import numpy as np
import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -88,6 +90,54 @@ class Ifc(blenderbim.core.tool.Ifc):
def get_object(cls, element):
return IfcStore.get_element(element.id())
@classmethod
def rebuild_element_maps(cls):
"""Rebuilds the id_map and guid_map
When any Blender object is stored outside a Blender PointerProperty,
such as in a regular Python list, there is the likely probability that
the object will be invalidated when undo or redo occurs. Object
invalidation seems to occur whenever an object is affected during an
operation, or selected, or has a related modifier, and so on ... to
cover all bases, this completely rebuilds the element maps.
"""
IfcStore.id_map = {}
IfcStore.guid_map = {}
if not cls.get():
return
for obj in bpy.data.objects:
bpy.msgbus.clear_by_owner(obj)
element = cls.get_entity(obj)
if not element:
continue
IfcStore.id_map[element.id()] = obj
global_id = getattr(element, "GlobalId", None)
if global_id:
IfcStore.guid_map[global_id] = obj
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
for obj in bpy.data.materials:
bpy.msgbus.clear_by_owner(obj)
material = cls.get_entity(obj)
style = tool.Style.get_style(obj)
if material:
IfcStore.id_map[material.id()] = obj
if style:
IfcStore.id_map[style.id()] = obj
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
@classmethod
def link(cls, element, obj):
IfcStore.link_element(element, obj)
+8 -7
View File
@@ -111,8 +111,12 @@ class Root(blenderbim.core.tool.Root):
if obj.data and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
if not obj.data and getattr(element, "ObjectType", None) == "TEXT":
return element.Representation.Representations[0]
if element.is_a("IfcTypeProduct"):
if element.RepresentationMaps:
return element.RepresentationMaps[0].MappedRepresentation
elif element.is_a("IfcProduct"):
if element.Representation:
return element.Representation.Representations[0]
@classmethod
def get_representation_context(cls, representation):
@@ -157,8 +161,5 @@ class Root(blenderbim.core.tool.Root):
@classmethod
def set_object_name(cls, obj, element):
name = obj.name
if "/" in name and name.split("/")[0][0:3] == "Ifc":
name = "/".join(name.split("/")[1:])
name = "{}/{}".format(element.is_a(), name)
obj.name = name
name = getattr(element, "Name", getattr(element, "AxisTag", None))
obj.name = "{}/{}".format(element.is_a(), name or "Unnamed")
+3 -3
View File
@@ -52,7 +52,7 @@ class Spatial(blenderbim.core.tool.Spatial):
if not structure.is_a("IfcSpatialStructureElement"):
return False
else:
if not structure.is_a("IfcSpatialElement"):
if not structure.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
return False
if not hasattr(element, "ReferencedInStructures"):
return False
@@ -204,7 +204,7 @@ class Spatial(blenderbim.core.tool.Spatial):
parent = tool.Ifc.get().by_type("IfcProject")[0]
for object in ifcopenshell.util.element.get_parts(parent) or []:
if object.is_a("IfcSpatialElement"):
if object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
cls.create_new_storey_li(object, 0)
cls.props.is_container_update_enabled = True
@@ -223,7 +223,7 @@ class Spatial(blenderbim.core.tool.Spatial):
new.has_children = True
if new.is_expanded:
for related_object in ifcopenshell.util.element.get_parts(element) or []:
if related_object.is_a("IfcSpatialElement"):
if related_object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
cls.create_new_storey_li(related_object, level_index + 1)
@classmethod
+43 -2
View File
@@ -15,7 +15,48 @@ All documentation is written in ReStructured Text and is available in the
The following colours and annotation styles should be used for annotating
images. All stroke widths are 3px with a corner radius of 3px. Horizontal
underlines are 5px with a corner radius of 2px. The dark green is **39b54a** and
the light green is **d9e021**.
underlines are 5px with a corner radius of 2px. The dark green is ``39b54a`` and
the light green is ``d9e021``.
.. image:: documentation-style.png
Special keywords such as **Technical Terminology** that the user should be
aware of should be bolded, titlecased, and used consistently. You *may*
use italics to emphasize words or phrases. Inline code must be ``quoted`` and
longer code snippets may use code blocks.
.. code-block:: console
$ cd /path/to/blenderbim
$ ls
Be sure to specify the language to enable syntax highlighting.
.. code-block:: python
print("Hello, world!")
A button may be used to point users to a critical sample file or
download.
.. container:: blockbutton
`Visit critical link <https://blenderbim.org>`__
.. note::
Instead of writing "Note that XYZ ..." you should use notes sparingly to
highlight "gotchas".
.. tip::
Tips may be used to add a useful but optional suggestion.
.. warning::
Warnings may be used to highlight common mistakes.
.. seealso::
See also blocks should be used to reference `further reading
<https://blenderbim.org>`__ links.
@@ -106,7 +106,7 @@ def update_geonodes_modifier():
bm.edges.index_update()
bm.faces.index_update()
if bpy.context.object.mode == "EDIT":
if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
+2 -2
View File
@@ -347,7 +347,7 @@ Scenario: Remove pset - multiple objects
Scenario: Edit pset length property
Given an empty IFC project
And I press "mesh.add_clever_stair"
And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Pset_StairFlightCommon').id()"
And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Pset_StairFlightCommon').id()"
And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())"
And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')"
@@ -380,7 +380,7 @@ Scenario: Edit qset length property
Given an empty IFC project
And I press "mesh.add_clever_stair"
And I press "bim.calculate_all_quantities"
And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Qto_StairFlightBaseQuantities').id()"
And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Qto_StairFlightBaseQuantities').id()"
And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())"
And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')"
+3
View File
@@ -492,6 +492,9 @@ class TestUpdateDrawingName:
drawing.does_file_exist("absolute_layout_uri").should_be_called().will_return(True)
drawing.update_embedded_svg_location("absolute_layout_uri", "old_location", "new_location").should_be_called()
drawing.is_editing_sheets().should_be_called().will_return(True)
drawing.import_sheets().should_be_called()
subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name")
@@ -13,5 +13,6 @@ system, as well as high level analysis and authoring functions.
ifcopenshell-python/hello_world
ifcopenshell-python/code_examples
ifcopenshell-python/geometry_processing
ifcopenshell-python/geometry_creation
ifcopenshell-python/geometry_tree
ifcopenshell-python/developer_guide
@@ -0,0 +1,350 @@
Geometry creation
=================
Walls, doors, slabs, and other physical products in IFC can be represented with
2D or 3D geometry. Most commonly, this geometry is created using graphical
frontends, like the BlenderBIM Add-on. IfcOpenShell can create and edit
geometry with code.
.. note::
Geometry is optional in IFC. For many usecases, geometry is not required,
such as in facility management.
General concepts
----------------
Any IFC element may have a location in the 3D world known as the **Object
Placement**. The **Object Placement** is the "local origin" of the object. This
is sometimes known as the object's center or insertion point in other software.
The **Object Placement** is typically somewhere at a corner, center, or
midpoint of the object. The **Object Placement** may be used to identify a
rough "coordinate" location of the object's start / center, and used as a
center of transformation when moving or rotating the object's geometry. The
object's geometry is always relative to the **Object Placement**.
.. note::
**Object Placements** are optional if the object has no geometry. However,
any object with a geometry must have an **Object Placement**.
IFC products may have multiple geometric representations, positioned relative
to the **Object Placement**. For example, a door might have a 3D body of a
"closed door" as one geometric representation, a 2D linework of an "open door"
intended to be shown in a plan, a 3D box showing the clearance of the door for
disabled access, and 3D dashed linework showing the hinge and swing of a door
in an elevation or section. Of course, you might not want to see all this
geometry at the same time. What you see depends on the context you are viewing
the door in.
For this reason, each one of these geometric representations is called a
**Representation**. Each **Representation** belongs to a **Representation
Context**. The **Representation Context** determines how the **Representation**
is intended to be viewed. For example, a "2D Plan View" might be a
**Representation Context**. This allows the user to choose to see the
appropriate **Representation**.
Project units
-------------
All coordinates in IFC are stored using project units. This means that prior to
creating **Object Placements** or **Representations** you have to define a
project length unit as a minimum.
Assuming you are creating a project from scratch with code, here is how you
might define units:
.. code-block:: python
# You need a project before you can assign units.
run("root.create_entity", model, ifc_class="IfcProject")
# Let's say we want coordinates to be in millimeters.
length = run("unit.add_si_unit", model, unit_type="LENGTHUNIT", prefix="MILLI")
run("unit.assign_unit", model, units=[length])
# Alternatively, you may specify without any arguments to automatically
# create millimeters, square meters, and cubic meters as a convenience for
# testing purposes. Sorry imperial folks, we prioritise metric here.
run("unit.assign_unit", model)
Object placements
-----------------
The **Object Placement** describes **Location** and **Rotation**. The
**Location** is given as an XYZ coordinate, and the **Rotation** is given as
two vectors: a local X axis and a local Z axis vector. The local Y axis vector
is derived via a right-handed coordinate system. This means that the global X
axis points to "Project East", the global Y axis points to "Project North", and
the global Z axis points up (i.e. to the sky). This coordinate system is the
same system used in Blender.
.. image:: images/object-placement.png
The recommended way to set an **Object Placement** is to specify the placement
as a 4x4 matrix. You can use the ``numpy`` library to create and edit matrices.
A 4x4 matrix looks like this:
.. code-block::
1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
0, 0, 0, 1
This type of matrix is known as the **Identity Matrix**. It represents no
translation (i.e. a location at the origin of ``0, 0, 0``) and no rotation
(i.e. the X axis is ``1, 0, 0``, the Y axis is ``0, 1, 0``, and the Z axis is
``0, 0, 1``). The numbers in the matrix correlate to the location and rotation
axes as follows:
.. code-block::
XAxis_X, YAxis_X, ZAxis_X, X
XAxis_Y, YAxis_Y, ZAxis_Y, Y
XAxis_Z, YAxis_Z, ZAxis_Z, Z
0, 0, 0, 1
Notice how the last line is always fixed to ``0, 0, 0, 1``. For example, here
is another matrix of an object at ``2, 3, 5`` that is rotated anti-clockwise by
90 degrees.
.. code-block::
0, -1, 0, 2
1, 0, 0, 3
0, 0, 1, 5
0, 0, 0, 1
.. image:: images/object-placement-example.png
Here's how we might do the same operation with Python code:
.. code-block:: python
import numpy
# Create a wall. Our wall currently has no object placement or representations.
wall = run("root.create_entity", model, ifc_class="IfcWall")
# Create a 4x4 identity matrix. This matrix is at the origin with no rotation.
matrix = numpy.eye(4)
# Rotate the matix 90 degrees anti-clockwise around the Z axis (i.e. in plan).
# Anti-clockwise is positive. Clockwise is negative.
matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix
# Set the X, Y, Z coordinates. Notice how we rotate first then translate.
# This is because the rotation origin is always at 0, 0, 0.
matrix[:,3][0:3] = (2, 3, 5)
# Set our wall's Object Placement using our matrix.
# `is_si=True` states that we are using SI units instead of project units.
run("geometry.edit_object_placement", model, product=wall, matrix=matrix, is_si=True)
Representation contexts
-----------------------
As an object may have multiple **Representations**, we need to use
**Representation Contexts** to distinguish the purpose and intended context of
each **Representation**.
A **Representation Context** is defined in terms of X paramters:
1. **Context Type**: 3D Model or 2D Plan
2. **Context Identifier**: The purpose of the **Representation**
3. **Target View**: The drafting convention of the **Representation**
4. **Target Scale**: The scale for the **representation** to be shown at
The **Context Type** must either be set to **Model** for 3D **Representations**
or **Plan** for 2D **Representations**.
The most common **Context Identifiers** you might use are:
- Body: for the actual physical shape of the object
- Box: the bounding box of the object (useful for shape analytics)
- Axis: the parametric line determining the shape of the object
- Profile: the elevation silhouette of the object, useful for cutting out holes
for the object to fit into host elements
- Footprint: the plan view silhouette of the object, useful for certain
quantity take-off rules
- Clearance: the clearance zone of the object
- Annotation: symbolic annotations typically used in diagrams or drawings
The most common **Target Views** you might use are:
- MODEL_VIEW: for general 3D geometry you might see in a BIM viewer or any
generic fallback representation
- PLAN_VIEW: for 2D geometry you might see in a plan representation
- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation
- SECTION_VIEW: for 2D geometry you might see in a section representation
- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams you
might use for structural frame analysis, axis-based parametric modeling
- SKETCH_VIEW: for viewing abstract high-level representations such as in
bubble diagrams of spatial topology
The vast majority of the time, you will only be interested in using a 3D Body
MODEL_VIEW **Representation Context**.
.. code-block:: python
# If we plan to store 3D geometry in our IFC model, we have to setup
# a "Model" context.
model3d = run("context.add_context", model, context_type="Model")
# And/Or, if we plan to store 2D geometry, we need a "Plan" context
plan = run("context.add_context", model, context_type="Plan")
# Now we setup the subcontexts with each of the geometric "purposes"
# we plan to store in our model. "Body" is by far the most important
# and common context, as most IFC models are assumed to be viewable
# in 3D.
body = run("context.add_context", model,
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
# The 3D Axis subcontext is important if any "axis-based" parametric
# geometry is going to be created. For example, a beam, or column
# may be drawn using a single 3D axis line, and for this we need an
# Axis subcontext.
run("context.add_context", model,
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
# It's also important to have a 2D Axis subcontext for things like
# walls and claddings which can be drawn using a 2D axis line.
run("context.add_context", model,
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
# The 3D Box subcontext is useful for clash detection or shape
# analysis, or even lazy-loading of large models.
run("context.add_context", model,
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
# A 2D annotation subcontext for plan views are important for door
# swings, window cuts, and symbols for equipment like GPOs, fire
# extinguishers, and so on.
run("context.add_context", model,
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
# You may also create 2D annotation subcontexts for sections and
# elevation views.
run("context.add_context", model,
context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
run("context.add_context", model,
context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
Representations
---------------
Once you have an **Object Placement** and a **Representation Context**, you can
now create a **Representation**.
Each **Representations** must choose a geometry modeling technique. For
example, you may specify a mesh-like geometry, which uses vertices, edges, and
faces. Alternatively, you may specify 2D profiles extruded into solid shapes
and potentially having boolean voids and subtractions. You may even specify
single edges and linework without any surfaces or solids. Representations may
even be single points, such as for survey points or structual point
connections.
After the **Representation** is created, you will need to assign the
**Representation** to the IFC object (e.g. wall, door, slab, etc). Here's the
general pattern in code:
.. code-block:: python
# Let's create a new project using millimeters with a single furniture element at the origin.
model = run("project.create_file")
run("root.create_entity", model, ifc_class="IfcProject")
run("unit.assign_unit", model)
# We want our representation to be the 3D body of the element.
# This representation context is only created once per project.
# You must reuse the same body context every time you create a new representation.
model3d = run("context.add_context", model, context_type="Model")
body = run("context.add_context", model,
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
# Create our element with an object placement.
element = run("root.create_entity", model, ifc_class="IfcFurniture")
run("geometry.edit_object_placement", model, product=element)
# Let's create our representation!
# See below sections for examples on how to create representations.
representation = ...
# Assign our new body representation back to our element
run("geometry.assign_representation", model, product=element, representation=representation)
Mesh representations
--------------------
Mesh **Representations** are specified in terms of a list of vertices, edges,
and faces. The faces may be triangles, quads, or n-gons. Faces may also contain
inner loops, or holes. Mesh **Representations** are most appropriately used for
complex shapes that only need to approximately represent physical products,
such as furniture or equipment, or flat, panellised design (e.g. triangulated
facade elements). Mesh **Representations** are also suitable for box-like
shapes that have bespoke indents, protrusions, TINs, textured, or as-built
geometry.
In IFC, meshes may be stored as **Faceted BReps**, **Tessellations**, or
**Triangulations** (specifically only for triangles).
.. code-block:: python
# These vertices and faces represent a 2m square 1m high pyramid in SI units.
# Note how they are nested lists. Each nested list represents a "mesh". There may be multiple meshes.
vertices = [[(0.,0.,0.), (0.,2.,0.), (2.,2.,0.), (2.,0.,0.), (1.,1.,1.)]]
faces = [[(0,1,2,3), (0,4,1), (1,4,2), (2,4,3), (3,4,0)]]
representation = run("geometry.add_mesh_representation", model, context=body, vertices=vertices, faces=faces)
.. image:: images/mesh-representation.png
Wall representations
--------------------
Wall-like **Representations** are simple blocks with a length, height, and
thickness. They are most appropriately used for walls, insulation, bulkhead
ends, cladding, and other uniformly thick blocks that extend along an imaginary
2D line in the XY plane.
.. note::
Even though the function is named ``add_wall_representation``, you may use
this geometry for any element, not just walls.
.. code-block:: python
# A wall-like representation, 5 meters long, 3 meters high, and 200mm thick
representation = run("geometry.add_wall_representation", model,
context=body, length=5, height=3, thickness=0.2)
.. image:: images/wall-representation.png
A wall-like **Representation** always starts at the **Object Placement** and
runs along the local +X axis. The thickness is always along the local Y axis.
This means that if you want the wall-like object to start and end at a
particular point, you have to set the **Object Placement** location and
rotation as appropriate. This can be done using the API:
.. code-block:: python
# A wall-like representation starting and ending at a particular 2D point
# It is not necessary to assign the representation after using this function.
run("geometry.create_2pt_wall", model,
element=element, context=body, p1=(1., 1.), p2=(3., 2.), elevation=0, height=3, thickness=0.2)
.. image:: images/wall-2pt-representation.png
Profile representations
-----------------------
Custom representations
----------------------
Manual representations
----------------------
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More