mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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 |
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')"
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user