Merge branch 'v0.8.0' into orientation_slots

This commit is contained in:
Bruno Postle
2025-02-24 22:07:27 +00:00
committed by GitHub
349 changed files with 5115 additions and 3335 deletions
+1 -3
View File
@@ -115,10 +115,8 @@ classes = [
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.RevertClippingPlaneCut,
operator.SelectDataDir,
operator.SelectCacheDir,
operator.SelectDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
operator.SetTab,
operator.SwitchTab,
+2 -1
View File
@@ -132,7 +132,8 @@ class IfcExporter:
bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
checksum = obj.data.BIMMeshProperties.material_checksum
mprops = tool.Geometry.get_mesh_props(obj.data)
checksum = mprops.material_checksum
return checksum != tool.Geometry.get_material_checksum(obj)
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
+21 -17
View File
@@ -60,7 +60,7 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
if props.is_renaming:
props.is_renmaing = False
return
IfcStore.get_file().by_id(ifc_definition_id).Name = obj.name
tool.Ifc.get().by_id(ifc_definition_id).Name = obj.name
refresh_ui_data()
return
@@ -71,7 +71,7 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
obj.BIMObjectProperties.is_renaming = False
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id)
if "/" in obj.name:
object_name = obj.name
element_name = obj.name.split("/", 1)[1]
@@ -221,35 +221,38 @@ def refresh_ui_data():
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
props = tool.Drawing.get_document_props()
props.should_draw_decorations = props.should_draw_decorations
if bpy.context.scene.WebProperties.is_connected:
tool.Web.send_webui_data()
@persistent
def loadIfcStore(scene):
def loadIfcStore(scene: bpy.types.Scene) -> None:
IfcStore.purge()
refresh_ui_data()
if not IfcStore.get_file():
if not tool.Ifc.get():
return
IfcStore.get_schema()
tool.Ifc.schema()
IfcStore.relink_all_objects()
@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(until_key=bpy.context.scene.BIMProperties.last_transaction)
def undo_post(scene: bpy.types.Scene) -> None:
props = tool.Blender.get_bim_props()
if IfcStore.last_transaction != props.last_transaction:
IfcStore.last_transaction = props.last_transaction
IfcStore.undo(until_key=props.last_transaction)
refresh_ui_data()
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(until_key=bpy.context.scene.BIMProperties.last_transaction)
def redo_post(scene: bpy.types.Scene) -> None:
props = tool.Blender.get_bim_props()
if IfcStore.last_transaction != props.last_transaction:
IfcStore.last_transaction = props.last_transaction
IfcStore.redo(until_key=props.last_transaction)
refresh_ui_data()
tool.Ifc.rebuild_element_maps()
@@ -282,7 +285,7 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
return pao
def viewport_shading_changed_callback(area):
def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
shading = area.spaces.active.shading.type
if shading == "RENDERED":
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
@@ -340,10 +343,11 @@ def load_post(scene):
tool.Blender.setup_tabs()
if tool.Ifc.get() and bpy.data.is_saved:
bpy.context.scene.BIMProperties.has_blend_warning = True
props = tool.Blender.get_bim_props()
props.has_blend_warning = True
# Bonsai overlays
georeference_props = bpy.context.scene.BIMGeoreferenceProperties
georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = tool.Model.get_model_props()
+10 -4
View File
@@ -27,12 +27,12 @@ import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING
if TYPE_CHECKING:
import bonsai.bim.prop
from bonsai.bim.prop import Attribute
from bonsai.bim.module.search.prop import BIMFilterGroup
# ImportCallback return values:
# - None - property should be imported by default workflow
@@ -119,7 +119,8 @@ def import_attributes(
data: dict[str, Any],
callback: Optional[ImportCallback] = None,
) -> None:
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
schema = tool.Ifc.schema()
for attribute in schema.declaration_by_name(ifc_class).all_attributes():
import_attribute(attribute, props, data, callback=callback)
@@ -372,11 +373,16 @@ def convert_property_group_from_si(property_group: bpy.types.PropertyGroup, skip
setattr(property_group, prop_name, prop_value)
def draw_filter(layout: bpy.types.UILayout, filter_groups, data, module: str) -> None:
def draw_filter(
layout: bpy.types.UILayout,
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup],
data,
module: str,
) -> None:
if not data.is_loaded:
data.load()
sprops = bpy.context.scene.BIMSearchProperties
sprops = tool.Search.get_search_props()
if tool.Ifc.get():
row = layout.row(align=True)
+16 -11
View File
@@ -105,7 +105,8 @@ class IfcStore:
@staticmethod
def get_file():
if IfcStore.file is None:
IfcStore.path = cast(str, bpy.context.scene.BIMProperties.ifc_file)
props = tool.Blender.get_bim_props()
IfcStore.path = props.ifc_file
# Interpret relative paths as relative to .blend file.
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
@@ -119,10 +120,11 @@ class IfcStore:
@staticmethod
def get_cache():
if IfcStore.cache is None and IfcStore.path:
props = tool.Blender.get_bim_props()
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
os.makedirs(bpy.context.scene.BIMProperties.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
os.makedirs(props.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
@@ -162,7 +164,8 @@ class IfcStore:
assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
props = tool.Blender.get_bim_props()
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
IfcStore.cache = None
try:
shutil.move(IfcStore.cache_path, new_cache_path)
@@ -178,6 +181,7 @@ class IfcStore:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
props = tool.Project.get_project_props()
if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
with zipfile.ZipFile(path, "r") as zip_ref:
@@ -187,7 +191,7 @@ class IfcStore:
return
elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
elif bpy.context.scene.BIMProjectProperties.should_stream:
elif props.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True)
else:
IfcStore.file = ifcopenshell.open(path)
@@ -195,9 +199,8 @@ class IfcStore:
@staticmethod
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
if IfcStore.file is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
bpy.context.scene.BIMProjectProperties.export_schema
)
props = tool.Project.get_project_props()
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(props.export_schema)
elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema_identifier)
return IfcStore.schema
@@ -247,7 +250,7 @@ class IfcStore:
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
obj.BIMMeshProperties.ifc_definition_id = element.id()
tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
return
existing_obj = IfcStore.id_map.get(element.id(), None)
@@ -414,7 +417,8 @@ class IfcStore:
method: Literal["EXECUTE", "INVOKE", "MODAL"] = "EXECUTE",
) -> set[str]:
bonsai.last_actions.append({"type": "operator", "name": operator.bl_idname})
bpy.context.scene.BIMProperties.is_dirty = True
props = tool.Blender.get_bim_props()
props.is_dirty = True
# Modals don't nest, and Blender handles the loop that continuously calls modal()
is_top_level_operator = not bool(IfcStore.current_transaction) or (method == "MODAL")
@@ -493,7 +497,8 @@ class IfcStore:
) -> None:
key = getattr(operator, "transaction_key", None)
data = getattr(operator, "transaction_data", None)
bpy.context.scene.BIMProperties.last_transaction = key
props = tool.Blender.get_bim_props()
props.last_transaction = key
IfcStore.last_transaction = key
rollback = rollback or getattr(operator, "rollback", lambda data: True)
commit = commit or getattr(operator, "commit", lambda data: True)
+23 -15
View File
@@ -69,6 +69,9 @@ class MaterialCreator:
if isinstance(mesh, bpy.types.Curve):
return
if mesh.get("has_layer_styles", None) == True:
return
self.mesh = mesh
self.obj = obj
if element.is_a("IfcTypeProduct"):
@@ -90,7 +93,8 @@ class MaterialCreator:
return # Already has materials assign to the representation itself
# Otherwise, we need to check for material styles on the element, since
# create_shape on types only works on representations.
context = tool.Ifc.get().by_id(self.mesh.BIMMeshProperties.ifc_definition_id).ContextOfItems
mprops = tool.Geometry.get_mesh_props(self.mesh)
context = tool.Ifc.get().by_id(mprops.ifc_definition_id).ContextOfItems
for material in ifcopenshell.util.element.get_materials(element):
if style := ifcopenshell.util.representation.get_material_style(material, context):
self.mesh["ios_materials"] = (style.id(),)
@@ -218,7 +222,7 @@ class IfcImporter:
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
classes_to_wireframe_str = bpy.context.scene.DocProperties.classes_to_wireframe
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None:
@@ -434,7 +438,7 @@ class IfcImporter:
return False
def calculate_model_offset(self) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if self.ifc_import_settings.false_origin_mode == "MANUAL":
tool.Loader.set_manual_blender_offset(self.file)
elif self.ifc_import_settings.false_origin_mode == "AUTOMATIC":
@@ -878,17 +882,19 @@ class IfcImporter:
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
if not bpy.context.scene.BIMProperties.ifc_file:
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
self.file = IfcStore.get_file()
props = tool.Blender.get_bim_props()
if not props.ifc_file:
props.ifc_file = self.ifc_import_settings.input_file
self.file = tool.Ifc.get()
def calculate_unit_scale(self):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
tool.Loader.set_unit_scale(self.unit_scale)
def set_units(self):
def set_units(self) -> None:
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
return # Geometry is optional in IFC
props = tool.Blender.get_bim_props()
for unit in assignment.Units:
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
if unit.is_a("IfcSIUnit"):
@@ -908,19 +914,19 @@ class IfcImporter:
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
try:
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
props.area_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
props.area_unit = "SQUARE_METRE"
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
try:
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
props.volume_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
props.volume_unit = "CUBIC_METRE"
def create_project(self):
project = self.file.by_type("IfcProject")[0]
@@ -964,10 +970,10 @@ class IfcImporter:
tool.Collector.assign(obj, should_clean_users_collection=False)
def is_curve_annotation(self, element: ifcopenshell.entity_instance) -> bool:
object_type = element.ObjectType
object_type = ifcopenshell.util.element.get_predefined_type(element)
return (
object_type in tool.Drawing.ANNOTATION_TYPES_DATA
and tool.Drawing.ANNOTATION_TYPES_DATA[object_type][3] == "curve"
and tool.Drawing.ANNOTATION_TYPES_DATA[object_type].data_type == "curve"
)
def get_drawing_group(self, element):
@@ -1056,12 +1062,13 @@ class IfcImporter:
else:
mesh["has_cartesian_point_offset"] = False
return tool.Loader.convert_geometry_to_mesh(
mesh = tool.Loader.convert_geometry_to_mesh(
geometry,
mesh,
verts=verts,
load_indexed_maps=self.ifc_import_settings.load_indexed_maps,
)
return tool.Loader.slice_layerset_mesh(element, mesh)
except:
self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
import traceback
@@ -1069,9 +1076,10 @@ class IfcImporter:
print(traceback.format_exc())
def set_default_context(self):
rprops = tool.Root.get_root_props()
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
rprops.contexts = str(subcontext.id())
break
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
@@ -23,7 +23,6 @@ import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.aggregate as core
import bonsai.core.spatial
from bonsai.bim.ifc import IfcStore
class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
+3 -3
View File
@@ -39,7 +39,7 @@ class BIM_PT_aggregate(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False
@@ -120,9 +120,9 @@ class BIM_PT_linked_aggregate(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False
return True
@@ -26,7 +26,6 @@ import bonsai.bim.helper
import bonsai.tool as tool
import bonsai.core.attribute as core
import bonsai.core.spatial
from bonsai.bim.ifc import IfcStore
def get_objs_for_operation(operator_properties, context):
@@ -117,7 +116,7 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
obj = tool.Blender.get_active_object(is_selected=False)
if not (element := tool.Ifc.get_entity(obj)):
return
@@ -17,7 +17,6 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bonsai.bim.ifc import IfcStore
from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -18,7 +18,6 @@
import bonsai.bim.helper
from bpy.types import Panel
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.attribute.data import AttributesData
import bonsai.tool as tool
@@ -113,7 +113,7 @@ class AuginCreateNewModel(bpy.types.Operator):
context.scene.collection.objects.link(cam_obj)
context.scene.camera = cam_obj
tmpdir = tempfile.mkdtemp()
tmpdir = tempfile.mkdtemp(dir=tool.Blender.get_addon_preferences().tmp_dir or None)
thumb_path = os.path.join(tmpdir, "thumb.png")
context.scene.render.image_settings.file_format = "PNG"
context.scene.render.filepath = thumb_path
@@ -122,15 +122,16 @@ class AuginCreateNewModel(bpy.types.Operator):
context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
bim_props = tool.Blender.get_bim_props()
client.upload_file(bim_props.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
# Notify done
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
payload = {
"user_token": props.token,
"ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
"model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
"ifc_filesize": os.path.getsize(bim_props.ifc_file),
"model_filesize": os.path.getsize(bim_props.ifc_file),
"thumb_filesize": os.path.getsize(thumb_path),
"model_upload_path": result["model_path"],
"thumb_upload_path": result["thumb_path"],
+3 -1
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy.types
import bonsai.tool as tool
class BIM_PT_augin(bpy.types.Panel):
@@ -47,7 +48,8 @@ class BIM_PT_augin(bpy.types.Panel):
row = layout.row()
row.label(text="Logged in as " + props.username)
if not context.scene.BIMProperties.ifc_file:
bim_props = tool.Blender.get_bim_props()
if not bim_props.ifc_file:
row = layout.row()
row.label(text="No IFC Found")
return
+5 -6
View File
@@ -43,7 +43,6 @@ import bonsai.tool as tool
import bonsai.bim.module.bcf.prop as bcf_prop
import bonsai.bim.module.bcf.bcfstore as bcfstore
from pathlib import Path
from bonsai.bim.ifc import IfcStore
from math import radians, degrees, atan, tan, cos, sin
from mathutils import Vector, Matrix, Euler, geometry
from xsdata.models.datatype import XmlDateTime
@@ -1206,7 +1205,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
return True
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
bcfxml = bcfstore.BcfStore.get_bcfxml()
assert bcfxml
@@ -1316,7 +1315,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
[0, 0, 0, 1],
)
)
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
matrix = ifcopenshell.util.geolocation.global2local(
@@ -1355,7 +1354,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
objs: list[bpy.types.Object] = []
for global_id in exception_global_ids:
obj = IfcStore.get_element(global_id)
obj = tool.Ifc.get_object_by_identifier(global_id)
if obj and context.view_layer.objects.get(obj.name):
assert isinstance(obj, bpy.types.Object)
objs.append(obj)
@@ -1414,7 +1413,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
return
bpy.ops.object.select_all(action="DESELECT")
for global_id in selected_global_ids:
obj = IfcStore.get_element(global_id)
obj = tool.Ifc.get_object_by_identifier(global_id)
if obj:
obj.select_set(True)
obj.hide_set(False)
@@ -1427,7 +1426,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
for acomponent in acoloring.component:
global_id_colours.setdefault(acomponent.ifc_guid, acoloring.color)
for global_id, color in global_id_colours.items():
obj = IfcStore.get_element(global_id)
obj = tool.Ifc.get_object_by_identifier(global_id)
if obj:
obj.color = self.hex_to_rgb(color)
@@ -25,6 +25,7 @@ import mathutils
import numpy as np
import multiprocessing
import ifcopenshell.api
import ifcopenshell.api.boundary
import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.shape
@@ -113,7 +114,7 @@ class Loader:
bm.edges.new((verts[-1], verts[0]))
bm.to_mesh(mesh)
bm.free()
mesh.BIMMeshProperties.ifc_definition_id = surface.id()
tool.Ifc.link(surface, mesh)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = mathutils.Matrix(
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
@@ -416,7 +417,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
tool.Boundary.move_origin_to_space_origin(context.active_object)
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
ifcopenshell.api.boundary.assign_connection_geometry(tool.Ifc.get(), **settings)
return {"FINISHED"}
+4 -5
View File
@@ -18,7 +18,6 @@
import bpy
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
import bonsai.tool as tool
from bonsai.bim.module.boundary.data import SpaceBoundariesData
@@ -34,7 +33,7 @@ class BIM_PT_SceneBoundaries(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
row = self.layout.row(align=True)
@@ -58,9 +57,9 @@ class BIM_PT_Boundary(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
entity = tool.Ifc.get().by_id(props.ifc_definition_id)
return entity.is_a("IfcRelSpaceBoundary")
def draw(self, context):
@@ -134,7 +133,7 @@ class BIM_PT_SpaceBoundaries(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
element = tool.Ifc.get_entity(context.active_object)
for ifc_class in ("IfcSpace", "IfcExternalSpatialElement"):
-1
View File
@@ -18,7 +18,6 @@
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
class BIM_PT_bsdd(Panel):
+25 -8
View File
@@ -69,10 +69,12 @@ class CadTool(WorkSpaceTool):
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
)
def draw_settings(context, layout, workspace_tool):
def draw_settings(
context: bpy.types.Context, layout: bpy.types.UILayout, workspace_tool: bpy.types.WorkSpaceTool
) -> None:
ui_context = str(context.region.type)
obj = context.active_object
if not obj or not obj.data:
if not obj or not (data := obj.data):
return
is_profile = tool.Geometry.is_profile_object_active()
if is_profile:
@@ -122,7 +124,10 @@ class CadTool(WorkSpaceTool):
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
elif (
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
and tool.Geometry.get_mesh_props(data).subshape_type == "AXIS"
):
add_header_apply_button(
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
)
@@ -141,7 +146,7 @@ class CadTool(WorkSpaceTool):
if (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["pset_data"]
and context.active_object.BIMRailingProperties.is_editing_path
and obj.BIMRailingProperties.is_editing_path
):
add_header_apply_button(
layout,
@@ -154,7 +159,7 @@ class CadTool(WorkSpaceTool):
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
and context.active_object.BIMRoofProperties.is_editing_path
and obj.BIMRoofProperties.is_editing_path
):
add_header_apply_button(
layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context
@@ -252,15 +257,27 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
def hotkey_S_Q(self):
element = tool.Ifc.get_entity(bpy.context.active_object)
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
obj = bpy.context.active_object
if not obj:
return
if not tool.Geometry.has_mesh_properties(data := obj.data):
return
element = tool.Ifc.get_entity(obj)
if not element:
return
mprops = tool.Geometry.get_mesh_props(data)
if mprops.subshape_type == "PROFILE":
if element.is_a("IfcProfileDef"):
bpy.ops.bim.edit_arbitrary_profile()
elif element.is_a("IfcRelSpaceBoundary"):
bpy.ops.bim.edit_boundary_geometry()
else:
bpy.ops.bim.edit_extrusion_profile()
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
elif mprops.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis()
def hotkey_S_R(self):
@@ -300,7 +300,7 @@ class SelectIfcClashResults(bpy.types.Operator):
def execute(self, context):
# TODO refactor into new clash results system
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
@@ -321,8 +321,9 @@ class SelectIfcClashResults(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
if scene.BIMProperties.ifc_file:
ifc_file = scene.BIMProperties.ifc_file
bim_props = tool.Blender.get_bim_props(scene)
if bim_props.ifc_file:
ifc_file = bim_props.ifc_file
if scene.library:
break
@@ -478,7 +479,7 @@ class SelectSmartGroup(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
return tool.Ifc.get() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
def execute(self, context):
selected_smart_group = context.scene.BIMClashProperties.active_smart_group
@@ -201,7 +201,8 @@ class EnableEditingClassification(bpy.types.Operator):
def execute(self, context):
def callback(name, prop, data):
if name == "ReferenceTokens":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
geo_props = tool.Georeference.get_georeference_props()
new = geo_props.projected_crs.add()
new.name = name
new.data_type = "string"
new.is_null = data[name] is None
@@ -239,7 +240,7 @@ class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
classification: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"classification.remove_classification",
tool.Ifc.get(),
@@ -21,7 +21,6 @@ import bonsai.bim.helper
import bonsai.tool as tool
import bonsai.bim.module.classification.prop as classification_prop
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.classification.data import (
ClassificationsData,
ClassificationReferencesData,
@@ -124,7 +123,7 @@ class ReferenceUI:
self.sprops = context.scene.BIMClassificationProperties
self.bprops = context.scene.BIMBSDDProperties
self.props = context.scene.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.draw_add_ui(context)
@@ -23,7 +23,6 @@ import ifcopenshell.api.constraint
import ifcopenshell.util.attribute
import bonsai.bim.helper
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
class LoadObjectives(bpy.types.Operator):
@@ -84,7 +83,7 @@ class AddObjective(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
result = ifcopenshell.api.run("constraint.add_objective", IfcStore.get_file())
result = ifcopenshell.api.run("constraint.add_objective", tool.Ifc.get())
bpy.ops.bim.load_objectives()
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
return {"FINISHED"}
@@ -116,7 +115,7 @@ class RemoveConstraint(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = context.scene.BIMConstraintProperties
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"constraint.remove_constraint", self.file, **{"constraint": self.file.by_id(self.constraint)}
)
@@ -16,8 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.constraint.data import ConstraintsData, ObjectConstraintsData
@@ -33,7 +33,7 @@ class BIM_PT_constraints(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not ConstraintsData.is_loaded:
@@ -81,7 +81,7 @@ class BIM_PT_object_constraints(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
@@ -93,7 +93,7 @@ class BIM_PT_object_constraints(Panel):
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMConstraintProperties
self.props = obj.BIMObjectConstraintProperties
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.draw_add_ui()
+2 -3
View File
@@ -19,7 +19,6 @@
import bpy
import ifcopenshell.api
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.classification.data import CostClassificationsData
from bonsai.bim.module.cost.data import CostSchedulesData, CostItemRatesData, CostItemQuantitiesData
from bonsai.bim.prop import StrProperty, Attribute
@@ -83,7 +82,7 @@ def update_cost_item_identification(self, context):
props = context.scene.BIMCostProperties
if not props.is_cost_update_enabled or self.identification == "XXX":
return
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
@@ -98,7 +97,7 @@ def update_cost_item_name(self, context):
props = context.scene.BIMCostProperties
if not props.is_cost_update_enabled or self.name == "Unnamed":
return
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
+1 -2
View File
@@ -21,7 +21,6 @@ import bonsai.bim.helper
import bonsai.bim.module.cost.prop as CostProp
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.cost.data import CostSchedulesData
from typing import Any
@@ -37,7 +36,7 @@ class BIM_PT_cost_schedules(Panel):
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
file = tool.Ifc.get()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
@@ -20,8 +20,8 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.util.element
import bonsai.tool as tool
from math import degrees, atan2
from bonsai.bim.ifc import IfcStore
from .api import Api
@@ -92,7 +92,7 @@ class RunAnalysis(bpy.types.Operator):
bl_label = "Run Analysis"
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.inputs = {
"floors": [],
"walls": [],
+2 -3
View File
@@ -27,7 +27,6 @@ import ifcopenshell
import ifcopenshell.util.selector
import bonsai.tool as tool
import bonsai.bim.module.drawing.scheduler as scheduler
from bonsai.bim.ifc import IfcStore
from bonsai.bim.handler import refresh_ui_data
from typing import TYPE_CHECKING
from collections import Counter
@@ -212,7 +211,7 @@ class ExportIfcCsv(bpy.types.Operator):
props = context.scene.CsvProperties
self.filepath = bpy.path.ensure_ext(self.filepath, f".{props.format}")
if props.should_load_from_memory:
ifc_file = IfcStore.get_file()
ifc_file = tool.Ifc.get()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
results = ifcopenshell.util.selector.filter_elements(
@@ -314,7 +313,7 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.CsvProperties
if props.should_load_from_memory:
ifc_file = IfcStore.get_file()
ifc_file = tool.Ifc.get()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
ifc_csv = ifccsv.IfcCsv()
+3 -3
View File
@@ -17,8 +17,8 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.bim.helper
import bonsai.tool as tool
from bpy.types import Panel
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.search.data import SearchData
@@ -37,7 +37,7 @@ class BIM_PT_ifccsv(Panel):
scene = context.scene
props = scene.CsvProperties
if IfcStore.get_file():
if tool.Ifc.get():
row = layout.row(align=True)
row.prop(props, "should_load_from_memory")
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
@@ -49,7 +49,7 @@ class BIM_PT_ifccsv(Panel):
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
if not IfcStore.get_file() or not props.should_load_from_memory:
if not tool.Ifc.get() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "csv_ifc_file")
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
+25 -21
View File
@@ -78,10 +78,10 @@ class PrintIfcFile(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def execute(self, context):
print(IfcStore.get_file().wrapped_data.to_string())
print(tool.Ifc.get().wrapped_data.to_string())
return {"FINISHED"}
@@ -101,13 +101,14 @@ class ConvertToBlender(bpy.types.Operator):
if tool.Geometry.has_mesh_properties(data):
if data.library:
continue
data.BIMMeshProperties.ifc_definition_id = 0
tool.Geometry.get_mesh_props(data).ifc_definition_id = 0
for material in bpy.data.materials:
if material.library:
continue
tool.Ifc.unlink(obj=material)
context.scene.BIMProperties.ifc_file = ""
context.scene.BIMDebugProperties.attributes.clear()
bim_props = tool.Blender.get_bim_props()
bim_props.ifc_file = ""
tool.Debug.get_debug_props().attributes.clear()
IfcStore.purge()
bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -160,7 +161,8 @@ class ProfileImportIFC(bpy.types.Operator):
if not tool.Ifc.get():
cls.poll_message_set("No IFC file loaded.")
return False
if not context.scene.BIMProperties.ifc_file:
bim_props = tool.Blender.get_bim_props()
if not bim_props.ifc_file:
cls.poll_message_set("Current IFC file is not saved.")
return False
return True
@@ -184,10 +186,10 @@ class CreateAllShapes(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
total = len(elements)
@@ -256,7 +258,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = tool.Ifc.get()
element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id))
element = self.file.by_id(self.step_id or int(tool.Debug.get_debug_props().step_id))
settings = ifcopenshell.geom.settings()
settings.set("keep-bounding-boxes", True)
if self.should_include_curves:
@@ -309,7 +311,7 @@ class RewindInspector(bpy.types.Operator):
bl_description = "Rewind the Inspector to the previously inspected element"
def execute(self, context):
props = context.scene.BIMDebugProperties
props = tool.Debug.get_debug_props()
total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2:
return {"FINISHED"}
@@ -328,13 +330,13 @@ class InspectFromStepId(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def execute(self, context):
self.file = IfcStore.get_file()
debug_props = context.scene.BIMDebugProperties
self.file = tool.Ifc.get()
debug_props = tool.Debug.get_debug_props()
debug_props.active_step_id = self.step_id
crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add()
crumb = debug_props.step_id_breadcrumb.add()
crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id)
debug_props.attributes.clear()
@@ -385,7 +387,7 @@ class InspectFromObject(bpy.types.Operator):
if (
(data := obj.data)
and tool.Geometry.has_mesh_properties(data)
and (ifc_id := data.BIMMeshProperties.ifc_definition_id)
and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
):
return ifc_id
@@ -422,7 +424,7 @@ class PrintObjectPlacement(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
placement = ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id))
placement = ifcopenshell.util.placement.get_local_placement(tool.Ifc.get().by_id(self.step_id))
if self.create_empty_object:
bpy.ops.object.empty_add(type="ARROWS")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -438,7 +440,8 @@ class ParseExpress(bpy.types.Operator):
bl_label = "Parse Express"
def execute(self, context):
core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file)
props = tool.Debug.get_debug_props()
core.parse_express(tool.Debug, props.express_file)
bonsai.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -452,8 +455,9 @@ class SelectExpressFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
def execute(self, context):
props = tool.Debug.get_debug_props()
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
context.scene.BIMDebugProperties.express_file = self.filepath
props.express_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -497,7 +501,7 @@ class PrintUnusedElementStats(bpy.types.Operator):
ignore_styled_items: bpy.props.BoolProperty(name="Ignore Styled Items", default=True)
def execute(self, context):
props = context.scene.BIMDebugProperties
props = tool.Debug.get_debug_props()
# ignore some classes that could have zero 0 inverse references by their nature
ignore_classes = []
if self.ignore_contexts:
@@ -543,7 +547,7 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
return True
def _execute(self, context):
props = context.scene.BIMDebugProperties
props = tool.Debug.get_debug_props()
if props.ifc_class_purge:
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
@@ -803,7 +807,7 @@ class DebugActiveDrawing(bpy.types.Operator):
)
def execute(self, context: bpy.types.Context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
drawing_item = props.drawings[props.active_drawing_index]
drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
+19 -6
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -28,6 +29,9 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal, get_args
DisplayType = Literal["BOUNDS", "WIRE", "SOLID", "TEXTURED"]
class BIMDebugProperties(PropertyGroup):
@@ -41,14 +45,23 @@ class BIMDebugProperties(PropertyGroup):
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File")
display_type: EnumProperty(
items=[
("BOUNDS", "Bounds", ""),
("WIRE", "Wire", ""),
("SOLID", "Solid", ""),
("TEXTURED", "Textured", ""),
],
items=[(display_type, display_type.capitalize(), "") for display_type in get_args(DisplayType)],
name="Display Type",
default="BOUNDS",
)
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
package_name: StringProperty(name="Package Name", default="")
if TYPE_CHECKING:
step_id: int
number_of_polygons: int
percentile_of_polygons: int
active_step_id: int
step_id_breadcrumb: bpy.types.bpy_prop_collection_idprop[StrProperty]
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
inverse_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
inverse_references: bpy.types.bpy_prop_collection_idprop[Attribute]
express_file: str
display_type: str
ifc_class_purge: str
package_name: str
+11 -11
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import Panel
@@ -32,10 +33,11 @@ class BIM_PT_debug(Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMDebugProperties
props = tool.Debug.get_debug_props()
bim_props = tool.Blender.get_bim_props()
row = self.layout.row(align=True)
row.prop(context.scene.BIMProperties, "ifc_file", text="")
row.prop(bim_props, "ifc_file", text="")
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
@@ -87,27 +89,25 @@ class BIM_PT_debug(Panel):
row.prop(props, "step_id", text="")
row = layout.split(factor=0.7, align=True)
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
row.operator("bim.select_high_polygon_meshes").threshold = props.number_of_polygons
row.prop(props, "number_of_polygons", text="")
row = layout.split(factor=0.7, align=True)
row.operator("bim.select_highest_polygon_meshes").percentile = (
context.scene.BIMDebugProperties.percentile_of_polygons
)
row.operator("bim.select_highest_polygon_meshes").percentile = props.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True)
row.prop(props, "display_type", text="")
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
row.operator("bim.override_display_type").display = props.display_type
layout.operator("bim.purge_unused_representations")
row = layout.row(align=True)
row.prop(context.scene.BIMDebugProperties, "ifc_class_purge", text="")
row.prop(props, "ifc_class_purge", text="")
row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH")
row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
if context.active_object and (data := context.active_object.data):
mprops = tool.Geometry.get_mesh_props(data)
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -123,7 +123,7 @@ class BIM_PT_debug(Panel):
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="")
row = layout.row(align=True)
row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id
row.operator("bim.inspect_from_step_id").step_id = props.active_step_id
row.operator("bim.inspect_from_object")
if props.attributes:
@@ -70,8 +70,9 @@ class VisualiseDiff(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
if scene.BIMProperties.ifc_file:
ifc_file = scene.BIMProperties.ifc_file
bim_props = tool.Blender.get_bim_props(scene)
if bim_props.ifc_file:
ifc_file = bim_props.ifc_file
if scene.library:
break
@@ -257,8 +258,9 @@ class SelectDiffObjects(bpy.types.Operator):
ifc_file = ""
for scene in obj.users_scene:
if scene.BIMProperties.ifc_file:
ifc_file = scene.BIMProperties.ifc_file
bim_props = tool.Blender.get_bim_props(scene)
if bim_props.ifc_file:
ifc_file = bim_props.ifc_file
if scene.library:
break
-1
View File
@@ -17,7 +17,6 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.diff.data import DiffData
import bonsai.bim.helper
import bonsai.tool as tool
@@ -52,7 +52,7 @@ class DocumentData:
@classmethod
def parent_document(cls):
props = bpy.context.scene.BIMDocumentProperties
props = tool.Document.get_document_props()
if len(props.breadcrumbs):
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3":
@@ -24,7 +24,6 @@ import ifcopenshell.util.element
import bonsai.bim.handler
import bonsai.tool as tool
import bonsai.core.document as core
from bonsai.bim.ifc import IfcStore
class LoadProjectDocuments(bpy.types.Operator):
@@ -116,7 +115,7 @@ class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMDocumentProperties
props = tool.Document.get_document_props()
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
@@ -30,6 +30,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING
def update_document_name(self: "Document", context: bpy.types.Context) -> None:
@@ -57,6 +58,11 @@ class Document(PropertyGroup):
)
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
identification: str
is_information: bool
ifc_definition_id: int
class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
@@ -65,3 +71,11 @@ class BIMDocumentProperties(PropertyGroup):
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index")
is_editing: BoolProperty(name="Is Editing", default=False)
if TYPE_CHECKING:
document_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_document_id: int
documents: bpy.types.bpy_prop_collection_idprop[Document]
breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
active_document_index: int
is_editing: bool
+6 -6
View File
@@ -16,8 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
@@ -33,13 +33,13 @@ class BIM_PT_documents(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not DocumentData.is_loaded:
DocumentData.load()
self.props = context.scene.BIMDocumentProperties
self.props = tool.Document.get_document_props()
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
@@ -92,7 +92,7 @@ class BIM_PT_object_documents(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
@@ -102,8 +102,8 @@ class BIM_PT_object_documents(Panel):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
self.props = tool.Document.get_document_props()
self.file = tool.Ifc.get()
self.draw_add_ui()
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os
import bpy
import math
@@ -125,7 +126,9 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(drawing: ifcopenshell.entity_instance, object_type: str, data_type: str) -> bpy.types.Object:
def get_annotation_obj(
drawing: ifcopenshell.entity_instance, object_type: str, data_type: tool.Drawing.ANNOTATION_DATA_TYPE
) -> bpy.types.Object:
camera = tool.Ifc.get_object(drawing)
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = tool.Drawing.get_camera_matrix(camera)
@@ -147,11 +150,17 @@ class Annotator:
collection.objects.link(obj)
return obj
if object_type != "ANGLE":
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if element and element.ObjectType == object_type and obj.type == object_type.upper():
return obj
# TODO: remove as outdated?
# Is reusing the same objects preventing the creation of new annotations.
# if object_type != "ANGLE":
# for obj in collection.objects:
# element = tool.Ifc.get_entity(obj)
# if (
# element
# and ifcopenshell.util.element.get_predefined_type(element) == object_type
# and obj.type == data_type.upper()
# ):
# return obj
if data_type == "mesh":
data = bpy.data.meshes.new(object_type)
+13 -6
View File
@@ -88,7 +88,8 @@ class SheetsData:
project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
if not titleblocks_dir:
titleblocks_dir = bpy.context.scene.DocProperties.titleblocks_dir
props = tool.Drawing.get_document_props()
titleblocks_dir = props.titleblocks_dir
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
if os.path.exists(titleblocks_dir):
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
@@ -120,23 +121,25 @@ class DrawingsData:
@classmethod
def location_hint(cls):
if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
props = tool.Drawing.get_document_props()
if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
results = [("0", "Origin", "")]
results.extend(
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
)
return results
elif bpy.context.scene.DocProperties.target_view in ["MODEL_VIEW"]:
elif props.target_view in ["MODEL_VIEW"]:
return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]]
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@classmethod
def active_drawing_pset_data(cls):
ifc_file = tool.Ifc.get()
drawing_id = bpy.context.scene.DocProperties.active_drawing_id
props = tool.Drawing.get_document_props()
drawing_id = props.active_drawing_id
if drawing_id == 0:
return {}
drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
drawing = ifc_file.by_id(drawing_id)
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
@@ -361,7 +364,11 @@ class DecoratorData:
element = tool.Ifc.get_entity(obj)
supported_object_types = ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS")
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types:
if (
not element
or not element.is_a("IfcAnnotation")
or ifcopenshell.util.element.get_predefined_type(element) not in supported_object_types
):
return None
dimension_style = "arrow"
@@ -423,7 +423,8 @@ class BaseDecorator:
# font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg
magic_font_scale = bpy.context.scene.DocProperties.magic_font_scale
props = tool.Drawing.get_document_props()
magic_font_scale = props.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1]
@@ -976,6 +977,7 @@ class FallDecorator(BaseDecorator):
# same function as in svgwriter.py
def get_label_text():
element = tool.Ifc.get_entity(obj)
assert element
B, A = [v.co.xyz for v in spline_points[:2]]
rise = abs(A.z - B.z)
O = A.copy()
@@ -988,13 +990,14 @@ class FallDecorator(BaseDecorator):
angle = 90
# ues SLOPE_ANGLE as default
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
object_type = ifcopenshell.util.element.get_predefined_type(element)
if object_type in ("FALL", "SLOPE_ANGLE"):
return f"{angle}°"
elif element.ObjectType == "SLOPE_FRACTION":
elif object_type == "SLOPE_FRACTION":
if angle == 90:
return "-"
return f"{self.format_value(context, rise)} / {self.format_value(context, run)}"
elif element.ObjectType == "SLOPE_PERCENT":
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
return f"{round(angle_tg * 100)} %"
@@ -2022,7 +2025,8 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
props = tool.Drawing.get_document_props()
if drawing_font := props.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__())
@@ -2045,7 +2049,7 @@ class DecorationsHandler:
if not element.is_a("IfcAnnotation"):
continue
object_type: Union[str, None] = element.ObjectType
object_type: Union[str, None] = ifcopenshell.util.element.get_predefined_type(element)
if object_type == "DRAWING":
continue
+23 -15
View File
@@ -19,6 +19,7 @@
import bpy
import blf
import gpu
import bonsai.tool as tool
from bpy import types
from mathutils import Vector
from mathutils import geometry
@@ -478,21 +479,25 @@ class ExtrusionWidget(types.GizmoGroup):
bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"}
@classmethod
def poll(cls, ctx):
obj = ctx.object
def poll(cls, context):
obj = context.active_object
return (
obj
and obj.type == "MESH"
and obj.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
and (data := obj.data)
and isinstance(data, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(data).ifc_parameters.get("IfcExtrudedAreaSolid/Depth") is not None
)
def setup(self, ctx):
target = ctx.object
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
def setup(self, context: bpy.types.Context) -> None:
target = context.object
assert target
mesh = target.data
assert isinstance(mesh, bpy.types.Mesh)
prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
basis = target.matrix_world.normalized()
theme = ctx.preferences.themes[0].user_interface
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
theme = context.preferences.themes[0].user_interface
scale_value = self.get_scale_value(context.scene.unit_settings.system, context.scene.unit_settings.length_unit)
# setup handle
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
@@ -521,23 +526,26 @@ class ExtrusionWidget(types.GizmoGroup):
# gz.use_draw_modal = True
# gz.target_set_prop('value', target.demo, 'depth')
def refresh(self, ctx):
def refresh(self, context: bpy.types.Context) -> None:
"""updating gizmos"""
target = ctx.object
target = context.active_object
basis = target.matrix_world.normalized()
self.handle.matrix_basis = basis
self.guides.matrix_basis = basis
def update(self, ctx):
def update(self, context: bpy.types.Context) -> None:
"""updating object"""
bpy.ops.bim.update_parametric_representation()
target = ctx.object
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
target = context.active_object
assert target
mesh = target.data
assert isinstance(mesh, bpy.types.Mesh)
prop = tool.Geometry.get_mesh_props(mesh).ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
self.handle.target_set_prop("offset", prop, "value")
self.guides.target_set_prop("depth", prop, "value")
@staticmethod
def get_scale_value(system, length_unit):
def get_scale_value(system: str, length_unit: str) -> float:
scale_value = 1
if system == "METRIC":
if length_unit == "KILOMETERS":
@@ -24,7 +24,8 @@ from bpy.app.handlers import persistent
@persistent
def load_post(*args):
if bpy.context.scene.DocProperties.should_draw_decorations:
props = tool.Drawing.get_document_props()
if props.should_draw_decorations:
decoration.DecorationsHandler.install(bpy.context)
else:
decoration.DecorationsHandler.uninstall()
@@ -35,9 +36,11 @@ def depsgraph_update_pre_handler(scene):
set_active_camera_resolution(scene)
def set_active_camera_resolution(scene):
if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings:
def set_active_camera_resolution(scene: bpy.types.Scene) -> None:
props = tool.Drawing.get_document_props()
if not scene.camera or "/" not in scene.camera.name or not props.drawings:
return
assert isinstance(scene.camera.data, bpy.types.Camera)
props = scene.camera.data.BIMCameraProperties
ortho_scale = max((props.width, props.height))
aspect_ratio = props.width / props.height
@@ -60,5 +63,3 @@ def set_active_camera_resolution(scene):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x)
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y)
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
+48 -4
View File
@@ -19,8 +19,10 @@
import bpy
import math
import mathutils.geometry
import ifcopenshell
import bonsai.tool as tool
from mathutils import Vector
from typing import Union
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
@@ -135,6 +137,9 @@ def format_distance(
scaleFactor = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system
unit_length = bpy.context.scene.unit_settings.length_unit
area_unit_symbol = " " + ifcopenshell.util.unit.get_unit_symbol(
ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "AREAUNIT")
)
value *= scaleFactor
@@ -225,7 +230,24 @@ def format_distance(
if add_inches or frac:
tx_dist += '"'
else:
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
fmt = "%1.3f"
sq_feet = round(value * toInches / inPerFoot, 4)
tx_dist = ""
if area_unit_symbol == " ft2":
fmt += area_unit_symbol
tx_dist = fmt % sq_feet
if area_unit_symbol == " in2":
sq_inch = sq_feet * 144
fmt += area_unit_symbol
tx_dist = fmt % sq_inch
if area_unit_symbol == " yd2":
sq_yard = sq_feet / 9
fmt += area_unit_symbol
tx_dist = fmt % sq_yard
if area_unit_symbol == " mi2":
sq_mile = sq_feet / 27878400
fmt += area_unit_symbol
tx_dist = fmt % sq_mile
# METRIC FORMATTING
elif unit_system == "METRIC":
@@ -287,16 +309,38 @@ def format_distance(
d_mm = value * (1000)
tx_dist = fmt % d_mm
if isArea:
tx_dist += s_code
if area_unit_symbol == " m2":
if decimal_places is None:
fmt = "%1.3f"
if hide_units is False:
fmt += area_unit_symbol
tx_dist = fmt % value
if area_unit_symbol == " cm2":
if decimal_places is None:
fmt = "%1.1f"
if hide_units is False:
fmt += area_unit_symbol
d_cm = value * (10000)
tx_dist = fmt % d_cm
if area_unit_symbol == " mm2":
if decimal_places is None:
fmt = "%1.0f"
if hide_units is False:
fmt += area_unit_symbol
d_cm = value * (1000000)
tx_dist = fmt % d_cm
else:
tx_dist = fmt % value
return tx_dist
def get_active_drawing(scene):
def get_active_drawing(
scene: bpy.types.Scene,
) -> Union[tuple[bpy.types.Collection, bpy.types.Camera], tuple[None, None]]:
"""Get active drawing collection and camera"""
props = scene.DocProperties
props = tool.Drawing.get_document_props()
try:
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.BIMObjectProperties.collection, camera
@@ -134,19 +134,19 @@ class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a drawing view to the IFC project"
def _execute(self, context):
self.props = context.scene.DocProperties
hint = self.props.location_hint
if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
props = tool.Drawing.get_document_props()
hint = props.location_hint
if props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
hint = int(hint)
core.add_drawing(
tool.Ifc,
tool.Collector,
tool.Drawing,
target_view=self.props.target_view,
target_view=props.target_view,
location_hint=hint,
)
try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
@@ -162,7 +162,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -176,7 +176,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "should_duplicate_annotations")
def _execute(self, context):
self.props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
core.duplicate_drawing(
tool.Ifc,
tool.Drawing,
@@ -184,7 +184,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
should_duplicate_annotations=self.should_duplicate_annotations,
)
try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
drawing = tool.Ifc.get().by_id(props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
@@ -244,7 +244,7 @@ class CreateDrawing(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
if self.print_all:
@@ -261,7 +261,7 @@ class CreateDrawing(bpy.types.Operator):
self.camera = context.scene.camera
self.camera_element = tool.Ifc.get_entity(self.camera)
self.camera_document = tool.Drawing.get_drawing_document(self.camera_element)
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
with profile("Drawing generation process"):
with profile("Initialize drawing generation process"):
@@ -385,7 +385,7 @@ class CreateDrawing(bpy.types.Operator):
obj.hide_render = obj.name not in visible_object_names
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
drawing_style = self.props.drawing_styles[self.cprops.active_drawing_style_index]
if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True)
@@ -712,9 +712,11 @@ class CreateDrawing(bpy.types.Operator):
}
cached_linework -= edited_guids
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
bim_props = tool.Blender.get_bim_props()
files = {bim_props.ifc_file: tool.Ifc.get()}
for link in context.scene.BIMProjectProperties.links:
props = tool.Project.get_project_props()
for link in props.links:
if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
files[link.name] = IfcStore.session_files[link.name]
@@ -729,7 +731,7 @@ class CreateDrawing(bpy.types.Operator):
# Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
ifc_cache_path = os.path.join(bim_props.cache_dir, f"{ifc_hash}.h5")
self.serialiser.setFile(ifc)
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
@@ -1141,7 +1143,8 @@ class CreateDrawing(bpy.types.Operator):
try:
return tool.Ifc.get().by_guid(guid)
except:
for link in bpy.context.scene.BIMProjectProperties.links:
props = tool.Project.get_project_props()
for link in props.links:
if link.name not in IfcStore.session_files:
IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
try:
@@ -1367,7 +1370,11 @@ class CreateDrawing(bpy.types.Operator):
elements = list(elements | filtered_drawing_annotations)
annotations = sorted(
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
elements,
key=lambda a: (
tool.Drawing.get_annotation_z_index(a),
1 if ifcopenshell.util.element.get_predefined_type(a) == "TEXT" else 0,
),
)
precision = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "MetricPrecision")
@@ -1458,7 +1465,8 @@ class AddSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a sheet to the project"
def _execute(self, context):
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
props = tool.Drawing.get_document_props()
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=props.titleblock)
class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
@@ -1474,7 +1482,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
cls.poll_message_set("Not implemented yet.")
return False
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -1483,7 +1491,7 @@ class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
pass
"""
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
core.duplicate_sheet(
tool.Ifc,
tool.Drawing,
@@ -1508,7 +1516,7 @@ class OpenLayout(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet)
@@ -1530,7 +1538,8 @@ class SelectAllSheets(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
for sheet in context.scene.DocProperties.sheets:
props = tool.Drawing.get_document_props()
for sheet in props.sheets:
if sheet.is_selected != self.select_all:
sheet.is_selected = self.select_all
return {"FINISHED"}
@@ -1550,7 +1559,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
@@ -1564,7 +1573,7 @@ class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context)
def execute(self, context):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
if self.open_all:
@@ -1612,9 +1621,10 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway.
if not props.sheets or not context.scene.BIMProperties.data_dir:
bim_props = tool.Blender.get_bim_props()
if not props.sheets or not bim_props.data_dir:
return False
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
@@ -1622,8 +1632,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
return True
def _execute(self, context):
props = context.scene.DocProperties
active_drawing = tool.Drawing.get_active_drawing_item()
props = tool.Drawing.get_document_props()
active_drawing = props.drawings[props.active_drawing_index]
assert active_drawing
active_sheet = tool.Drawing.get_active_sheet(context)
@@ -1680,7 +1690,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
active_item = tool.Drawing.get_active_sheet_item()
if active_item is None:
return False
@@ -1714,11 +1724,12 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
return props.sheets and context.scene.BIMProperties.data_dir
bim_props = tool.Blender.get_bim_props()
return props.sheets and bim_props.data_dir
def invoke(self, context, event):
# opening all sheets on shift+click
@@ -1731,7 +1742,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
scene = context.scene
props = scene.DocProperties
props = tool.Drawing.get_document_props()
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
@@ -1838,7 +1849,8 @@ class SelectAllDrawings(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
for drawing in context.scene.DocProperties.drawings:
props = tool.Drawing.get_document_props()
for drawing in props.drawings:
if drawing.is_selected != self.select_all:
drawing.is_selected = self.select_all
return {"FINISHED"}
@@ -1857,7 +1869,7 @@ class OpenDrawing(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -1871,7 +1883,7 @@ class OpenDrawing(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if self.open_all:
drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in self.props.drawings if d.is_drawing and d.is_selected
@@ -1907,7 +1919,7 @@ class ActivateModel(bpy.types.Operator):
bl_description = "Activate the model view, hide all annotations"
def execute(self, context):
dprops = bpy.context.scene.DocProperties
dprops = tool.Drawing.get_document_props()
dprops.active_drawing_id = 0
CutDecorator.uninstall()
@@ -1971,11 +1983,12 @@ class ActivateDrawingBase:
return self.execute(context)
def execute(self, context):
if bpy.context.scene.DocProperties.is_editing_drawings == False:
props = tool.Drawing.get_document_props()
if props.is_editing_drawings == False:
bpy.ops.bim.load_drawings()
drawing = tool.Ifc.get().by_id(self.drawing)
dprops = bpy.context.scene.DocProperties
dprops = tool.Drawing.get_document_props()
if self.use_quick_preview:
tool.Blender.activate_camera(tool.Drawing.import_temporary_drawing_camera(drawing))
@@ -2027,7 +2040,7 @@ class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -2050,7 +2063,7 @@ class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_sheet_item(reference_type="DRAWING"):
cls.poll_message_set("No drawing selected.")
return False
@@ -2066,7 +2079,8 @@ class SelectDocIfcFile(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
context.scene.DocProperties.ifc_files[self.index].name = self.filepath
props = tool.Drawing.get_document_props()
props.ifc_files[self.index].name = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -2098,7 +2112,7 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -2112,12 +2126,10 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context)
def _execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if self.remove_all:
drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id)
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
]
else:
if not self.drawing:
@@ -2187,7 +2199,8 @@ class ReloadDrawingStyles(bpy.types.Operator):
with open(json_path, "r") as fi:
shading_styles_json = json.load(fi)
drawing_styles = context.scene.DocProperties.drawing_styles
props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
drawing_styles.clear()
styles = [style for style in shading_styles_json]
for style_name in styles:
@@ -2212,7 +2225,8 @@ class AddDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
drawing_styles = context.scene.DocProperties.drawing_styles
props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
new = drawing_styles.add()
# drawing style is saved to ifc on rename
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
@@ -2227,7 +2241,8 @@ class RemoveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
context.scene.DocProperties.drawing_styles.remove(self.index)
props = tool.Drawing.get_document_props()
props.drawing_styles.remove(self.index)
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
@@ -2283,7 +2298,8 @@ class SaveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
index = int(self.index)
else:
index = context.scene.camera.data.BIMCameraProperties.active_drawing_style_index
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
props = tool.Drawing.get_document_props()
props.drawing_styles[index].raster_style = json.dumps(style)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
@@ -2309,7 +2325,8 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
if not DrawingsData.is_loaded:
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
drawing_styles = context.scene.DocProperties.drawing_styles
props = tool.Drawing.get_document_props()
drawing_styles = props.drawing_styles
rel_path = drawing_pset_data["ShadingStyles"]
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
@@ -2338,7 +2355,7 @@ class SaveDrawingStylesData(bpy.types.Operator, tool.Ifc.Operator):
new_style_name = None
ifc_file = tool.Ifc.get()
drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id)
drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
@@ -2357,17 +2374,18 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
scene = context.scene
ifc_file = tool.Ifc.get()
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
props = tool.Drawing.get_document_props()
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
if active_drawing_style_index >= len(props.drawing_styles):
self.report({"ERROR"}, "Could not find active drawing style")
return {"CANCELLED"}
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
self.drawing_style = props.drawing_styles[active_drawing_style_index]
self.set_raster_style(context)
self.set_query(context)
drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id)
drawing = ifc_file.by_id(props.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
@@ -2392,7 +2410,8 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def set_query(self, context: bpy.types.Context) -> None:
self.include_global_ids = []
self.exclude_global_ids = []
for ifc_file in context.scene.DocProperties.ifc_files:
props = tool.Drawing.get_document_props()
for ifc_file in props.ifc_files:
try:
ifc = ifcopenshell.open(ifc_file.name)
except:
@@ -2507,14 +2526,15 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not props.schedules:
cls.poll_message_set("No schedule selected.")
return False
return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
bim_props = tool.Blender.get_bim_props()
return props.schedules and props.sheets and bim_props.data_dir
def _execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
active_schedule = props.schedules[props.active_schedule_index]
active_sheet = tool.Drawing.get_active_sheet(context)
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
@@ -2573,14 +2593,15 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not props.references:
cls.poll_message_set("No reference selected.")
return False
return props.references and props.sheets and context.scene.BIMProperties.data_dir
bim_props = tool.Blender.get_bim_props()
return props.references and props.sheets and bim_props.data_dir
def _execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
active_reference = props.references[props.active_reference_index]
active_sheet = tool.Drawing.get_active_sheet(context)
extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
@@ -2682,7 +2703,8 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add()
dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.add()
return {"FINISHED"}
@@ -2695,7 +2717,8 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {"FINISHED"}
@@ -2979,7 +3002,7 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.load_sheets(tool.Drawing)
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
sheets_not_found = []
for sheet_prop in props.sheets:
if not sheet_prop.is_sheet:
@@ -3009,7 +3032,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
document_type: Literal["SHEET", "TITLEBLOCK", "EMBEDDED"]
def invoke(self, context, event):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if sheet.is_a("IfcDocumentInformation"):
self.document_type = "SHEET"
@@ -3023,6 +3046,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
props = tool.Drawing.get_document_props()
if self.document_type == "SHEET":
row = self.layout.row()
row.prop(self, "identification", text="Identification")
@@ -3030,13 +3054,13 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "name", text="Name")
elif self.document_type == "TITLEBLOCK":
row = self.layout.row()
row.prop(context.scene.DocProperties, "titleblock", text="Titleblock")
row.prop(props, "titleblock", text="Titleblock")
elif self.document_type == "EMBEDDED":
row = self.layout.row()
row.prop(self, "identification", text="Identification")
def _execute(self, context):
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
if self.document_type == "SHEET":
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
@@ -3134,7 +3158,7 @@ class ExpandTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = True
core.load_drawings(tool.Drawing)
@@ -3150,7 +3174,7 @@ class ContractTargetView(bpy.types.Operator):
target_view: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = False
core.load_drawings(tool.Drawing)
@@ -3166,7 +3190,7 @@ class ExpandSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = True
core.load_sheets(tool.Drawing)
@@ -3182,7 +3206,7 @@ class ContractSheet(bpy.types.Operator):
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = False
core.load_sheets(tool.Drawing)
@@ -3373,7 +3397,7 @@ class ConvertSVGToDXF(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
props = tool.Drawing.get_document_props()
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
@@ -3387,14 +3411,13 @@ class ConvertSVGToDXF(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
props = tool.Drawing.get_document_props()
if self.convert_all:
drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id)
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
tool.Ifc.get().by_id(d.ifc_definition_id) for d in props.drawings if d.is_drawing and d.is_selected
]
else:
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
drawings = [tool.Ifc.get().by_id(props.drawings.get(self.view).ifc_definition_id)]
drawing_uris: list[Path] = []
drawings_not_found: list[str] = []
+5 -5
View File
@@ -76,7 +76,7 @@ def update_diagram_scale(self, context):
try:
element = (
tool.Ifc.get()
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
.by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
@@ -93,7 +93,7 @@ def update_diagram_scale(self, context):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
def update_is_nts(self, context):
def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
if not self.update_props:
return
if not context.scene.camera or context.scene.camera.data != self.id_data:
@@ -104,7 +104,7 @@ def update_is_nts(self, context):
try:
element = (
tool.Ifc.get()
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
.by_id(tool.Geometry.get_mesh_props(self.id_data).ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
@@ -192,8 +192,8 @@ def get_drawing_style_name(self: "DrawingStyle"):
def set_drawing_style_name(self: "DrawingStyle", new_value: str) -> None:
"""ensure the name is unique"""
scene = bpy.context.scene
drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name]
props = tool.Drawing.get_document_props()
drawing_styles = [s.name for s in props.drawing_styles if s.name != self.name]
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
old_value = self.name
self["name"] = new_value
@@ -56,7 +56,7 @@ def a1_to_rc(cell):
class Scheduler:
def schedule(self, infile, outfile):
def schedule(self, infile: str, outfile: str) -> None:
self.svg = svgwrite.Drawing(
outfile,
debug=False,
@@ -71,11 +71,12 @@ class Scheduler:
elif infile.endswith("xlsx"):
self.schedule_xlsx(infile, outfile)
def parse_css(self, infile):
def parse_css(self, infile: str) -> None:
props = tool.Drawing.get_document_props()
stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path):
stylesheet_rel_path = getattr(bpy.context.scene.DocProperties, "schedules_stylesheet_path")
ifc_file_path = os.path.dirname(IfcStore.path)
stylesheet_rel_path = props.schedules_stylesheet_path
ifc_file_path = os.path.dirname(tool.Ifc.get_path())
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path):
stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css")
@@ -91,7 +92,7 @@ class Scheduler:
self.svg.defs.add(self.svg.style(css))
def schedule_xlsx(self, infile, outfile):
def schedule_xlsx(self, infile: str, outfile: str) -> None:
workbook = openpyxl.open(infile, data_only=True)
sheet = workbook.active
@@ -236,7 +237,7 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True)
def schedule_ods(self, infile, outfile):
def schedule_ods(self, infile: str, outfile: str) -> None:
doc = load_ods(infile)
# useful for debugging ods
@@ -495,11 +496,11 @@ class Scheduler:
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True)
def get_style(self, style_name, styles):
def get_style(self, style_name: str, styles: dict) -> dict:
style = styles[style_name] if style_name else {}
return style
def get_box_alignment(self, style):
def get_box_alignment(self, style: dict) -> str:
if style and "vertical-align" in style and style["vertical-align"] != "automatic":
vertical_align = style["vertical-align"]
else:
@@ -181,35 +181,35 @@ class SvgWriter:
self.decimal_places = decimal_places
for element in annotations:
obj = tool.Ifc.get_object(element)
if not obj or element.ObjectType == "DRAWING":
if not obj or (object_type := ifcopenshell.util.element.get_predefined_type(element)) == "DRAWING":
continue
elif element.ObjectType == "GRID":
elif object_type == "GRID":
self.draw_grid_annotation(obj)
elif element.ObjectType == "TEXT_LEADER":
elif object_type == "TEXT_LEADER":
self.draw_leader_annotation(obj)
elif element.ObjectType == "STAIR_ARROW":
elif object_type == "STAIR_ARROW":
self.draw_stair_annotation(obj)
elif element.ObjectType == "DIMENSION":
elif object_type == "DIMENSION":
self.draw_dimension_annotations(obj)
elif element.ObjectType == "ANGLE":
elif object_type == "ANGLE":
self.draw_angle_annotations(obj)
elif element.ObjectType == "RADIUS":
elif object_type == "RADIUS":
self.draw_radius_annotations(obj)
elif element.ObjectType == "DIAMETER":
elif object_type == "DIAMETER":
self.draw_diameter_annotations(obj)
elif element.ObjectType == "ELEVATION":
elif object_type == "ELEVATION":
self.draw_elevation_annotation(obj)
elif element.ObjectType == "SECTION":
elif object_type == "SECTION":
self.draw_section_annotation(obj)
elif element.ObjectType == "BREAKLINE":
elif object_type == "BREAKLINE":
self.draw_break_annotations(obj)
elif element.ObjectType == "PLAN_LEVEL":
elif object_type == "PLAN_LEVEL":
self.draw_plan_level_annotation(obj)
elif element.ObjectType == "SECTION_LEVEL":
elif object_type == "SECTION_LEVEL":
self.draw_section_level_annotation(obj)
elif element.ObjectType == "TEXT":
elif object_type == "TEXT":
self.draw_text_annotation(obj, obj.location)
elif element.ObjectType in ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
elif object_type in ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.draw_fall_annotations(obj)
else:
self.draw_misc_annotation(obj)
@@ -1207,13 +1207,14 @@ class SvgWriter:
angle = 90
# ues SLOPE_ANGLE as default
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
object_type = ifcopenshell.util.element.get_predefined_type(element)
if object_type in ("FALL", "SLOPE_ANGLE"):
return f"{angle}°"
elif element.ObjectType == "SLOPE_FRACTION":
elif object_type == "SLOPE_FRACTION":
if angle == 90:
return "-"
return f"{helper.format_distance(rise, precision=self.precision, decimal_places=self.decimal_places)} / {helper.format_distance(run, precision=self.precision, decimal_places=self.decimal_places)}"
elif element.ObjectType == "SLOPE_PERCENT":
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
return f"{round(angle_tg * 100)} %"
+7 -7
View File
@@ -49,7 +49,7 @@ class BIM_PT_camera(Panel):
return
self.layout.use_property_split = True
dprops = context.scene.DocProperties
dprops = tool.Drawing.get_document_props()
props = context.scene.camera.data.BIMCameraProperties
col = self.layout.column(align=True)
@@ -161,7 +161,7 @@ class BIM_PT_drawing_underlay(Panel):
layout.use_property_split = True
camera = context.scene.camera
assert camera
dprops = context.scene.DocProperties
dprops = tool.Drawing.get_document_props()
props = camera.data.BIMCameraProperties
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
@@ -229,7 +229,7 @@ class BIM_PT_drawings(Panel):
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_drawings:
row = self.layout.row(align=True)
@@ -302,7 +302,7 @@ class BIM_PT_schedules(Panel):
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_schedules:
row = self.layout.row(align=True)
@@ -352,7 +352,7 @@ class BIM_PT_references(Panel):
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_references:
row = self.layout.row(align=True)
@@ -394,7 +394,7 @@ class BIM_PT_sheets(Panel):
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if not self.props.is_editing_sheets:
row = self.layout.row(align=True)
@@ -601,7 +601,7 @@ class BIM_UL_drawinglist(bpy.types.UIList):
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.prop(item, "name", text="", emboss=False)
self.props = context.scene.DocProperties
self.props = tool.Drawing.get_document_props()
if (
self.props.drawings
and self.props.active_drawing_id
@@ -19,6 +19,7 @@
import os
import bpy
import ifcopenshell.util.element
import bonsai.core.type
import bonsai.core.drawing as core
import bonsai.tool as tool
@@ -197,6 +198,8 @@ def create_annotation_occurrence(context):
class AnnotationToolUI:
layout: bpy.types.UILayout
@classmethod
def draw(cls, context, layout):
cls.layout = layout
@@ -224,7 +227,8 @@ class AnnotationToolUI:
@classmethod
def draw_create_object_interface(cls):
row = cls.layout.row(align=True)
row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations")
props = tool.Drawing.get_document_props()
row.prop(props, "should_draw_decorations", text="Viewport Annotations")
@classmethod
def draw_edit_object_interface(cls, context):
@@ -330,7 +334,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if not element or not element.is_a("IfcAnnotation"):
continue
annotation_type = element.ObjectType
annotation_type = ifcopenshell.util.element.get_predefined_type(element)
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
+2 -3
View File
@@ -18,7 +18,6 @@
import bonsai.tool as tool
from bpy.types import Panel
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.fm.data import FMData
@@ -40,11 +39,11 @@ class BIM_PT_fm(Panel):
scene = context.scene
props = scene.BIMFMProperties
if IfcStore.get_file():
if tool.Ifc.get():
row = layout.row()
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
if not tool.Ifc.get() or not props.should_load_from_memory:
row = layout.row()
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
@@ -98,12 +98,14 @@ addon_keymaps = []
@persistent
def block_scale(scene):
def block_scale(scene: bpy.types.Scene) -> None:
import bonsai.tool as tool
if obj := (getattr(bpy.context, "active_object", None) or bpy.context.view_layer.objects.active):
if isinstance(obj, bpy.types.Object) and obj.BIMObjectProperties.ifc_definition_id:
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
elif isinstance(obj, bpy.types.Mesh) and obj.BIMMeshProperties.ifc_definition_id:
elif isinstance(obj, bpy.types.Mesh) and tool.Geometry.get_mesh_props(obj).ifc_definition_id:
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
+12 -10
View File
@@ -53,15 +53,16 @@ class ViewportData:
obj = bpy.context.active_object
element = tool.Ifc.get_entity(obj)
modes = [obj_mode]
if bpy.context.scene.BIMGeometryProperties.representation_obj:
modes: list[tuple[str, str, str, str, int]] = [obj_mode]
gprops = tool.Geometry.get_geometry_props()
if gprops.representation_obj:
modes.append(item_mode)
if not obj:
return modes
if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
pprops = tool.Project.get_project_props()
if obj in pprops.clipping_planes_objs:
pass
elif element:
if tool.Geometry.is_locked(element):
@@ -107,8 +108,9 @@ class RepresentationsData:
element = tool.Ifc.get_entity(obj)
active_representation_id = None
if obj.data and hasattr(obj.data, "BIMMeshProperties"):
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
active_representation = tool.Geometry.get_active_representation(obj)
if active_representation:
active_representation_id = active_representation.id()
for representation in tool.Geometry.get_representations_iter(element):
representation_type = representation.RepresentationType
@@ -158,9 +160,9 @@ class RepresentationsData:
if not obj.data:
return []
element = tool.Ifc.get_entity(obj)
if not (active_representation_id := obj.data.BIMMeshProperties.ifc_definition_id):
base_representation = tool.Geometry.get_active_representation(obj)
if not base_representation:
return [] # Maybe in profile editing mode
base_representation = tool.Ifc.get().by_id(active_representation_id)
# shape aspects matching context of the active representation
matching_shape_aspects = []
@@ -390,7 +392,7 @@ class PlacementData:
def load(cls):
cls.data = {"has_placement": cls.has_placement()}
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
obj = bpy.context.active_object
if obj and props.has_blender_offset:
xyz = cls.original_xyz(obj)
@@ -413,7 +415,7 @@ class PlacementData:
@classmethod
def original_xyz(cls, obj):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
xyz = ifcopenshell.util.geolocation.xyz2enh(
obj.matrix_world[0][3],
obj.matrix_world[1][3],
@@ -46,12 +46,14 @@ class ItemDecorator:
obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]] = {}
objs: dict[str, dict[str, list]] = {}
obj_matrix: dict[str, Matrix] = {}
for item_obj in context.scene.BIMGeometryProperties.item_objs:
props = tool.Geometry.get_geometry_props()
for item_obj in props.item_objs:
if obj := item_obj.obj:
obj: bpy.types.Object
objs[obj.name] = cls.get_obj_data(obj)
obj_is_selected[obj.name] = obj.select_get()
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
item = tool.Geometry.get_active_representation(obj)
assert item
obj_is_boolean[obj.name] = [i for i in tool.Ifc.get().get_inverse(item) if i.is_a("IfcBooleanResult")]
obj_matrix[obj.name] = obj.matrix_world.copy()
@@ -143,7 +145,8 @@ class ItemDecorator:
color = selected_elements_color
blf.color(font_id, *color)
for item in context.scene.BIMGeometryProperties.item_objs:
props = tool.Geometry.get_geometry_props()
for item in props.item_objs:
if (obj := item.obj) and obj.hide_get() == False:
if obj.select_get():
centroid = obj.matrix_world @ Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
+167 -104
View File
@@ -23,12 +23,14 @@ import numpy as np
import numpy.typing as npt
import ifcopenshell
import ifcopenshell.api.layer
import ifcopenshell.api.style
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit
import ifcopenshell.api
import ifcopenshell.api.boundary
import ifcopenshell.api.grid
import bonsai.core.geometry
import bonsai.core.geometry as core
@@ -77,7 +79,8 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
self.separate_element(element)
def separate_item(self, context, obj):
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
item = tool.Geometry.get_active_representation(obj)
assert item
if tool.Geometry.is_meshlike_item(item):
previous_selected_objects = context.selected_objects
bpy.ops.mesh.separate(type=self.type)
@@ -85,7 +88,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
if obj in previous_selected_objects:
continue
self.add_meshlike_item(obj)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
else:
self.report({"INFO"}, f"Separating an {item.is_a()} is not supported")
@@ -120,7 +123,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
representation.Items = list(representation.Items) + [item]
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Ifc.link(item, obj)
props.add_item_object(obj, item)
def separate_element(self, element):
@@ -301,11 +304,12 @@ class AddRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
props = tool.Geometry.get_geometry_props()
row = self.layout.row()
row.prop(self, "representation_conversion_method", text="")
if self.representation_conversion_method == "OBJECT":
row = self.layout.row()
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
row.prop(props, "representation_from_object", text="")
class SelectConnection(bpy.types.Operator, tool.Ifc.Operator):
@@ -456,13 +460,17 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def update_obj_mesh_representation(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
data = obj.data
assert tool.Geometry.is_data_supported_for_adding_representation(data)
mprops = tool.Geometry.get_mesh_props(data)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
# or it's just present on the element. In theory, we can also allow editing representations
# if we know that representation wasn't affected by existing openings.
has_openings = tool.Geometry.has_openings(product) and obj.data.BIMMeshProperties.has_openings_applied
has_openings = tool.Geometry.has_openings(product) and tool.Geometry.get_mesh_props(data).has_openings_applied
if has_openings and not self.apply_openings:
# Meshlike things with openings can only be updated without openings applied.
if self.from_ui:
@@ -479,13 +487,14 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
elif product.is_a("IfcRelSpaceBoundary"):
# TODO refactor
settings = tool.Boundary.get_assign_connection_geometry_settings(obj)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
ifcopenshell.api.boundary.assign_connection_geometry(tool.Ifc.get(), **settings)
return
if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
old_representation = tool.Geometry.get_active_representation(obj)
assert old_representation
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
if self.ifc_representation_class == "IfcTessellatedFaceSet":
# We are explicitly casting to a tessellation, so remove all parametric materials.
@@ -544,12 +553,12 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj)
]
ifcopenshell.api.run(
"style.assign_representation_styles",
props = tool.Geometry.get_geometry_props()
ifcopenshell.api.style.assign_representation_styles(
self.file,
shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
should_use_presentation_style_assignment=props.should_use_presentation_style_assignment,
)
tool.Geometry.record_object_materials(obj)
@@ -568,8 +577,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
continue
representation.RepresentationIdentifier = "Reference"
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
tool.Ifc.link(new_representation, data)
data.name = tool.Loader.get_mesh_name(new_representation)
# TODO: In simple scenarios, a type has a ShapeRepresentation of ID
# 123. This is then mapped through mapped representations by
@@ -585,7 +594,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
# transformations.
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
if obj.data.BIMMeshProperties.ifc_parameters:
if mprops.ifc_parameters:
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
@@ -597,12 +606,13 @@ class UpdateParametricRepresentation(bpy.types.Operator):
@classmethod
def poll(cls, context):
return context.active_object and context.active_object.mode == "OBJECT"
return (obj := context.active_object) and obj.mode == "OBJECT" and tool.Geometry.has_mesh_properties(obj.data)
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
obj = context.active_object
props = obj.data.BIMMeshProperties
assert obj and tool.Geometry.has_mesh_properties(obj.data)
props = tool.Geometry.get_mesh_props(obj.data)
parameter = props.ifc_parameters[self.index]
self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
show_representation_parameters = bool(props.ifc_parameters)
@@ -626,8 +636,10 @@ class GetRepresentationIfcParameters(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
obj = context.active_object
assert obj and tool.Geometry.has_mesh_properties((data := obj.data))
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
parameters = tool.Geometry.get_mesh_props(data).ifc_parameters
self.report({"INFO"}, f"{len(parameters)} parameters found.")
@@ -671,7 +683,7 @@ class CopyRepresentation(bpy.types.Operator, tool.Ifc.Operator):
)
def lock_error_message(name):
def lock_error_message(name: str) -> str:
return f"'{name}' is locked. Unlock it via the Spatial panel in the Project Overview tab."
@@ -719,8 +731,10 @@ class OverrideDelete(bpy.types.Operator):
if self.is_batch:
row = self.layout.row()
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
row = self.layout.row()
row.label(text="See system console for deletion progress.")
def _execute(self, context):
def _execute(self, context: bpy.types.Context):
start_time = time()
if self.is_batch:
@@ -728,10 +742,19 @@ class OverrideDelete(bpy.types.Operator):
self.process_arrays(context)
clear_active_object = True
for obj in context.selected_objects:
try:
obj.name
except:
objects_to_remove = context.selected_objects
for i, obj in enumerate(objects_to_remove, 1):
# Log time.
time_since_start = time() - start_time
is_valid_data_block = tool.Blender.is_valid_data_block(obj)
if time_since_start > 10:
obj_name = f" ({obj.name})" if is_valid_data_block else ""
print(
f"Removing object {i}/{len(objects_to_remove)}{obj_name}. "
f"Time since start: {time_since_start:.2f} seconds."
)
if not is_valid_data_block:
continue
element = tool.Ifc.get_entity(obj)
if element:
@@ -781,7 +804,7 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
def process_arrays(self, context):
def process_arrays(self, context: bpy.types.Context) -> None:
selected_objects = set(context.selected_objects)
array_parents = set()
for obj in context.selected_objects:
@@ -824,7 +847,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
# unintended IFC spatial modifications. To make life less confusing for
# the user, Delete means Delete. End of story.
# Deep magick from the dawn of time
if IfcStore.get_file():
if tool.Ifc.get():
return IfcStore.execute_ifc_operator(self, context)
# https://blender.stackexchange.com/questions/203729/python-get-selected-objects-in-outliner
objects_to_delete = set()
@@ -948,7 +971,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
@staticmethod
def execute_duplicate_operator(self, context, linked=False):
# Deep magick from the dawn of time
if IfcStore.get_file():
if tool.Ifc.get():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
@@ -1029,7 +1052,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
# Unlink from previous boolean element
# and keep object tracked for decorations.
if is_tracked_opening:
new_obj.data.BIMMeshProperties.ifc_boolean_id = 0
mprops = tool.Geometry.get_mesh_props(new_obj.data)
mprops.ifc_boolean_id = 0
tool.Root.add_tracked_opening(new_obj, tracked_opening_type)
if obj == context.active_object:
@@ -1053,7 +1077,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
if new.is_a("IfcRelSpaceBoundary"):
surface = new.ConnectionGeometry.SurfaceOnRelatingElement
temp_data.name = f"0/{surface.id()}"
temp_data.BIMMeshProperties.ifc_definition_id = surface.id()
tool.Ifc.link(surface, temp_data)
else:
tool.Blender.remove_data_block(temp_data)
@@ -1089,12 +1113,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
@staticmethod
def duplicate_item(obj: bpy.types.Object) -> None:
props = tool.Geometry.get_geometry_props()
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
item = tool.Geometry.get_active_representation(obj)
assert item
new_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
new_obj = obj.copy()
assert tool.Geometry.has_mesh_properties(obj.data)
temp_data = obj.data.copy()
new_obj.data = temp_data
new_obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
tool.Ifc.link(new_item, temp_data)
new_obj.name = obj.data.name = f"Item/{new_item.is_a()}/{new_item.id()}"
props.add_item_object(new_obj, new_item)
@@ -1670,7 +1696,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
def join_item(self) -> None:
props = tool.Geometry.get_geometry_props()
ifc_file = tool.Ifc.get()
item = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
item = tool.Geometry.get_active_representation(self.target)
assert item
if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(self.target)
item_objs = [i.obj for i in props.item_objs if i.obj]
@@ -1693,7 +1720,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
for item_data in items_data:
props.add_item_object(item_data["obj"], ifc_file.by_id(item_data["ifc_definition_id"]))
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
bpy.context.view_layer.update()
tool.Root.reload_item_decorator()
@@ -1701,7 +1728,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
builder = ShapeBuilder(ifc_file)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
representation = tool.Geometry.get_active_representation(self.target)
assert representation
representation_type = representation.RepresentationType
if representation_type in ("Tessellation", "Brep"):
for obj in bpy.context.selected_objects:
@@ -1740,7 +1768,8 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
continue
# Only objects of the same representation type can be joined
obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
obj_rep = tool.Geometry.get_active_representation(obj)
assert obj_rep
if obj_rep.RepresentationType != representation_type:
obj.select_set(False)
self.report(
@@ -1891,9 +1920,10 @@ class OverrideEscape(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
if context.scene.BIMGeometryProperties.mode == "ITEM":
props = tool.Geometry.get_geometry_props()
if props.mode == "ITEM":
tool.Geometry.disable_item_mode()
elif context.scene.BIMGeometryProperties.mode == "EDIT":
elif props.mode == "EDIT":
bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False)
tool.Geometry.disable_item_mode()
elif tool.Model.get_model_props().openings:
@@ -1949,6 +1979,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def handle_single_object(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
props = tool.Geometry.get_geometry_props()
pprops = tool.Project.get_project_props()
if obj == props.representation_obj:
self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly")
elif obj in [o.obj for o in context.scene.BIMAggregateProperties.not_editing_objects]:
@@ -1956,7 +1987,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.report(
{"ERROR"}, f"Element '{obj.name}' does not belong to this aggregate and cannot be edited directly"
)
elif obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs:
elif obj in pprops.clipping_planes_objs:
self.report({"ERROR"}, "Clipping planes cannot be edited")
elif element:
if not obj.data:
@@ -2004,12 +2035,15 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
self.handle_single_object(context, obj)
def enable_editing_representation_item(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
element = tool.Ifc.get_entity(context.scene.BIMGeometryProperties.representation_obj)
item = tool.Geometry.get_active_representation(obj)
assert item
element = tool.Ifc.get_entity(tool.Geometry.get_geometry_props().representation_obj)
if tool.Geometry.is_meshlike_item(item):
tool.Geometry.dissolve_triangulated_edges(obj)
tool.Blender.select_and_activate_single_object(context, obj)
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
assert isinstance(mesh := obj.data, bpy.types.Mesh)
props = tool.Geometry.get_mesh_props(mesh)
props.mesh_checksum = tool.Geometry.get_mesh_checksum(mesh)
self.enable_edit_mode(context)
elif (
item.is_a("IfcSweptAreaSolid")
@@ -2026,21 +2060,21 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
f"Couldn't import profile, editing it directly is not yet supported. Failing profile: {profile}.",
)
return
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
elif item.is_a("IfcAnnotationFillArea"):
tool.Model.import_annotation_fill_area(item, obj=obj)
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
elif tool.Geometry.is_curvelike_item(item):
tool.Model.import_curve(item, obj=obj)
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Ifc.link(item, obj.data)
self.enable_edit_mode(context)
ProfileDecorator.install(context)
if not bpy.app.background:
@@ -2051,10 +2085,11 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context: bpy.types.Context) -> Union[None, set[str]]:
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"}
context.scene.BIMGeometryProperties.is_changing_mode = True
if context.scene.BIMGeometryProperties.mode != "EDIT":
context.scene.BIMGeometryProperties.mode = "EDIT"
context.scene.BIMGeometryProperties.is_changing_mode = False
props = tool.Geometry.get_geometry_props()
props.is_changing_mode = True
if props.mode != "EDIT":
props.mode = "EDIT"
props.is_changing_mode = False
def has_aggregates(self, objs):
for obj in objs:
@@ -2101,14 +2136,15 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.toggle_edit_mode(context)
context.scene.BIMGeometryProperties.is_changing_mode = True
if context.scene.BIMGeometryProperties.representation_obj:
if context.scene.BIMGeometryProperties.mode != "ITEM":
context.scene.BIMGeometryProperties.mode = "ITEM"
props = tool.Geometry.get_geometry_props()
props.is_changing_mode = True
if props.representation_obj:
if props.mode != "ITEM":
props.mode = "ITEM"
else:
if context.scene.BIMGeometryProperties.mode != "OBJECT":
context.scene.BIMGeometryProperties.mode = "OBJECT"
context.scene.BIMGeometryProperties.is_changing_mode = False
if props.mode != "OBJECT":
props.mode = "OBJECT"
props.is_changing_mode = False
if context.active_object and self.should_save:
element = tool.Ifc.get_entity(context.active_object)
@@ -2154,24 +2190,27 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
else:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
elif obj.data.BIMMeshProperties.ifc_definition_id:
if not tool.Geometry.has_geometric_data(obj):
elif representation := tool.Geometry.get_active_representation(obj):
if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False
self.should_save = False
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
assert tool.Geometry.has_mesh_properties(obj.data)
mesh_props = tool.Geometry.get_mesh_props(obj.data)
if tool.Geometry.is_meshlike(
representation
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
) and mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj)
else:
tool.Ifc.finish_edit(obj)
elif element.is_a("IfcGridAxis"):
if not tool.Geometry.has_geometric_data(obj):
if not tool.Geometry.is_geometric_data(obj.data):
self.is_valid = False
self.should_save = False
if obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
assert tool.Geometry.has_mesh_properties(obj.data)
mesh_props = tool.Geometry.get_mesh_props(obj.data)
if mesh_props.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
else:
tool.Ifc.finish_edit(obj)
@@ -2196,9 +2235,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def edit_representation_item(self, obj: bpy.types.Object) -> None:
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
props = tool.Geometry.get_geometry_props()
item = tool.Geometry.get_active_representation(obj)
assert item
if tool.Geometry.is_meshlike_item(item):
if tool.Geometry.has_geometric_data(obj) and obj.data.polygons:
if tool.Geometry.is_geometric_data(obj.data) and obj.data.polygons:
tool.Geometry.edit_meshlike_item(obj)
else:
tool.Geometry.import_item(obj)
@@ -2219,11 +2260,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
element = tool.Ifc.get_entity(bpy.context.scene.BIMGeometryProperties.representation_obj)
element = tool.Ifc.get_entity(props.representation_obj)
# Only certain classes should have a footprint
if element.is_a() in ("IfcSlab", "IfcRamp"):
footprint_context = ifcopenshell.util.representation.get_context(
@@ -2276,9 +2317,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = profile.id()
tool.Ifc.link(profile, obj.data)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
elif tool.Geometry.is_curvelike_item(item):
@@ -2304,10 +2345,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.util.element.replace_attribute(inverse, item, new)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = new.id()
tool.Ifc.link(new, obj.data)
tool.Geometry.import_item(obj)
props = tool.Geometry.get_geometry_props()
for item in additional_curves:
representation = tool.Geometry.get_active_representation(props.representation_obj)
representation = ifcopenshell.util.representation.resolve_representation(representation)
@@ -2316,7 +2356,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
name = f"Item/{item.is_a()}/{item.id()}"
mesh = bpy.data.meshes.new(name)
new_obj = bpy.data.objects.new(name, mesh)
new_obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Ifc.link(item, new_obj.data)
bpy.context.collection.objects.link(new_obj)
props.add_item_object(new_obj, item)
new_obj.matrix_world = obj.matrix_world
@@ -2329,10 +2369,11 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
def enable_edit_mode(self, context):
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"}
context.scene.BIMGeometryProperties.is_changing_mode = True
if context.scene.BIMGeometryProperties.mode != "EDIT":
context.scene.BIMGeometryProperties.mode = "EDIT"
context.scene.BIMGeometryProperties.is_changing_mode = False
props = tool.Geometry.get_geometry_props()
props.is_changing_mode = True
if props.mode != "EDIT":
props.mode = "EDIT"
props.is_changing_mode = False
class FlipObject(bpy.types.Operator):
@@ -2369,12 +2410,13 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
item.tags += ","
item.tags += tag
if obj.data and hasattr(obj.data, "BIMMeshProperties"):
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
representation = tool.Ifc.get().by_id(active_representation_id)
if tool.Geometry.has_mesh_properties((data := obj.data)):
representation = tool.Geometry.get_data_representation(data)
assert representation
# Shape aspects must be considered from the PartOfProductDefinitionShape level
element = tool.Ifc.get_entity(obj)
assert element
product_reps = []
if element.is_a("IfcProduct"):
product_reps = [element.Representation]
@@ -2439,7 +2481,8 @@ class DisableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
obj.BIMGeometryProperties.is_editing = False
assert obj
tool.Geometry.get_object_geometry_props(obj).is_editing = False
class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -2450,9 +2493,12 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
if context.scene.BIMGeometryProperties.representation_obj:
if tool.Geometry.get_geometry_props().representation_obj:
return False # Artificial restriction for now to prevent removing when in item mode
if not (obj := tool.Geometry.get_active_or_representation_obj()) or len(obj.BIMGeometryProperties.items) <= 1:
if (
not (obj := tool.Geometry.get_active_or_representation_obj())
or len(tool.Geometry.get_object_geometry_props(obj).items) <= 1
):
cls.poll_message_set(
"Active object need to have more than 1 representation items to keep representation valid"
)
@@ -2487,13 +2533,17 @@ class SelectRepresentationItem(bpy.types.Operator):
def execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
item = tool.Ifc.get().by_id(obj.BIMGeometryProperties.active_item.ifc_definition_id)
obj_props = tool.Geometry.get_object_geometry_props(obj)
assert obj_props.active_item
item = tool.Ifc.get().by_id(obj_props.active_item.ifc_definition_id)
item_ids = self.get_nested_item_ids(item)
props = tool.Geometry.get_geometry_props()
for item_obj in props.item_objs:
if item_obj.obj.data.BIMMeshProperties.ifc_definition_id in item_ids:
tool.Blender.select_object(item_obj.obj)
obj_ = item_obj.obj
props = tool.Geometry.get_mesh_props(obj_.data)
if props.ifc_definition_id in item_ids:
tool.Blender.select_object(obj_)
return {"FINISHED"}
def get_nested_item_ids(self, item):
@@ -2511,7 +2561,7 @@ class SelectRepresentationItem(bpy.types.Operator):
def poll_editing_representation_item_style(cls, context):
if not (obj := tool.Geometry.get_active_or_representation_obj()):
return False
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
if not props.is_editing:
return False
if not (item := props.active_item):
@@ -2546,7 +2596,8 @@ class EnableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = True
ifc_file = tool.Ifc.get()
@@ -2565,7 +2616,8 @@ class EditRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False
ifc_file = tool.Ifc.get()
@@ -2587,7 +2639,7 @@ class DisableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operato
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_style = False
@@ -2603,7 +2655,8 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
active_obj = tool.Geometry.get_active_or_representation_obj()
active_props = active_obj.BIMGeometryProperties
assert active_obj
active_props = tool.Geometry.get_object_geometry_props(active_obj)
active_props.is_editing_item_style = False
# Get active representation item
@@ -2670,7 +2723,8 @@ class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Op
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = True
# set dropdown to currently active shape aspect
@@ -2686,11 +2740,13 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False
ifc_file = tool.Ifc.get()
assert props.active_item
representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id)
@@ -2743,7 +2799,8 @@ class DisableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.O
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing_item_shape_aspect = False
@@ -2754,10 +2811,12 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, tool.Ifc.Opera
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
assert obj
element = tool.Ifc.get_entity(obj)
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
ifc_file = tool.Ifc.get()
assert props.active_item
representation_item_id = props.active_item.ifc_definition_id
representation_item = ifc_file.by_id(representation_item_id)
shape_aspect = ifc_file.by_id(props.active_item.shape_aspect_id)
@@ -2840,7 +2899,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
boolean_ids.add(item.SecondOperand.id())
continue
item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}")
item_mesh.BIMMeshProperties.ifc_definition_id = item_id
tool.Ifc.link(item, item_mesh)
item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh)
item_obj.matrix_world = obj.matrix_world
@@ -2870,7 +2929,7 @@ class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
tool.Geometry.sync_item_positions()
tool.Geometry.update_item_attributes(obj)
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(tool.Geometry.get_geometry_props().representation_obj)
tool.Geometry.import_item(obj)
tool.Root.reload_item_decorator()
@@ -2887,6 +2946,10 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
options={"SKIP_SAVE"},
)
if TYPE_CHECKING:
extrusion_item_obj: str
profile_name: str
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -2901,7 +2964,7 @@ class NameProfile(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
extrusion_item_obj = bpy.data.objects[self.extrusion_item_obj]
mesh_props = extrusion_item_obj.data.BIMMeshProperties
mesh_props = tool.Geometry.get_mesh_props(extrusion_item_obj.data)
extrusion = ifc_file.by_id(mesh_props.ifc_definition_id)
assert extrusion.is_a("IfcSweptAreaSolid")
profile = extrusion.SweptArea
@@ -2971,9 +3034,9 @@ class AddMeshlikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Root.reload_item_decorator()
@@ -3019,10 +3082,10 @@ class AddSweptAreaSolidItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
tool.Root.reload_item_decorator()
@@ -3090,10 +3153,10 @@ class AddCurvelikeItem(bpy.types.Operator, tool.Ifc.Operator):
props.add_item_object(obj, item)
representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
tool.Geometry.import_item_attributes(obj)
@@ -3139,10 +3202,10 @@ class AddHalfSpaceSolidItem(bpy.types.Operator, tool.Ifc.Operator):
representation = ifcopenshell.util.representation.resolve_representation(representation)
representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.reload_representation(props.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
tool.Geometry.get_mesh_props(obj.data).ifc_definition_id = item.id()
tool.Geometry.import_item(obj)
# TODO refactor to core and not rely on selection
@@ -3168,7 +3231,7 @@ class OverrideMove(bpy.types.Operator):
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
if tool.Ifc.get():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
@@ -32,7 +32,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import Optional, TYPE_CHECKING, Union
from typing import Optional, TYPE_CHECKING, Union, Literal
def get_contexts(self, context):
@@ -270,6 +270,9 @@ class BIMObjectGeometryProperties(PropertyGroup):
representation_item_layer: str
GeometryMode = Literal["OBJECT", "ITEM", "EDIT"]
class BIMGeometryProperties(PropertyGroup):
# Revit workaround
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
@@ -308,7 +311,7 @@ class BIMGeometryProperties(PropertyGroup):
should_force_faceted_brep: bool
should_force_triangulation: bool
is_changing_mode: bool
mode: str
mode: GeometryMode
representation_obj: Union[bpy.types.Object, None]
item_objs: bpy.types.bpy_prop_collection_idprop[RepresentationItemObject]
representation_from_object: Union[bpy.types.Object, None]
+23 -20
View File
@@ -20,7 +20,6 @@ import bpy
import bonsai.bim
import bonsai.tool as tool
from bpy.types import Panel, Menu, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import prop_with_search
from bonsai.bim.module.geometry.data import (
RepresentationsData,
@@ -53,9 +52,10 @@ def mode_menu(self, context):
UIData.load()
ifc_icon = f"{UIData.data['menu_icon_color_mode']}_ifc"
row = self.layout.row(align=True)
if context.scene.BIMGeometryProperties.mode == "EDIT":
props = tool.Geometry.get_geometry_props()
if props.mode == "EDIT":
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
row.prop(props, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
def object_menu(self, context):
@@ -336,9 +336,9 @@ class BIM_PT_connections(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not ConnectionsData.is_loaded:
@@ -411,15 +411,18 @@ class BIM_PT_mesh(Panel):
@classmethod
def poll(cls, context):
return (
context.active_object is not None
and context.active_object.type == "MESH"
and hasattr(context.active_object.data, "BIMMeshProperties")
and context.active_object.data.BIMMeshProperties.ifc_definition_id
(obj := context.active_object) is not None
and (mesh := obj.data)
and isinstance(mesh, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
)
def draw(self, context):
if not context.active_object.data:
return
obj = context.active_object
assert obj
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
row = self.layout.row()
row.label(text="Advanced Users Only", icon="ERROR")
@@ -427,7 +430,7 @@ class BIM_PT_mesh(Panel):
row = layout.row()
text = "Manually Save Representation"
if tool.Ifc.is_edited(context.active_object):
if tool.Ifc.is_edited(obj):
text += "*"
row.operator("bim.update_representation", text=text)
@@ -454,8 +457,8 @@ class BIM_PT_mesh(Panel):
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
if True:
mprops = tool.Geometry.get_mesh_props(mesh)
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
@@ -477,7 +480,7 @@ class BIM_PT_placement(Panel):
@classmethod
def poll(cls, context):
return context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id
return (obj := context.active_object) and obj.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not PlacementData.is_loaded:
@@ -571,10 +574,10 @@ class BIM_PT_workarounds(Panel):
@classmethod
def poll(cls, context):
return (
context.active_object is not None
and context.active_object.type == "MESH"
and hasattr(context.active_object.data, "BIMMeshProperties")
and context.active_object.data.BIMMeshProperties.ifc_definition_id
(obj := context.active_object) is not None
and (mesh := obj.data)
and isinstance(mesh, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(mesh).ifc_definition_id
)
def draw(self, context):
@@ -588,7 +591,7 @@ class BIM_PT_workarounds(Panel):
class BIM_UL_representation_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(self, context, layout: bpy.types.UILayout, data, item, icon, active_data, active_propname):
if item:
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
row = layout.row(align=True)
@@ -167,7 +167,7 @@ class GeoreferenceData:
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
result["x"], result["y"], result["z"] = wcs[:, 3][:3]
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
result["x"],
@@ -193,7 +193,7 @@ class GeoreferenceData:
@classmethod
def local_origin(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if not props.has_blender_offset:
return
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -21,6 +21,7 @@ import blf
import gpu
import bmesh
import ifcopenshell
import ifcopenshell.util.geolocation
import bonsai.tool as tool
from math import radians
from bpy.types import SpaceView3D
@@ -53,7 +54,8 @@ class GeoreferenceDecorator:
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
self.scale = bpy.context.scene.BIMGeoreferenceProperties.visualization_scale
props = tool.Georeference.get_georeference_props()
self.scale = props.visualization_scale
content_pos = [v * self.scale for v in content_pos]
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
@@ -64,7 +66,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return
@@ -177,7 +179,7 @@ class GeoreferenceDecorator:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if not props.model_origin: # If this is empty, no georeferencing data has been loaded.
return
@@ -350,7 +352,7 @@ class GeoreferenceDecorator:
self.gn_angle = float(GeoreferenceData.data["map_derived_angle"] or 0)
self.tn_angle = float(GeoreferenceData.data["true_derived_angle"] or 0)
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
blender_angle = ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
@@ -33,15 +33,18 @@ from bpy.props import (
)
from bonsai.bim.module.georeference.data import GeoreferenceData
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from typing import TYPE_CHECKING
def get_coordinate_operation_class(self, context):
def get_coordinate_operation_class(
self: "BIMGeoreferenceProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
return GeoreferenceData.data["coordinate_operation_class"]
def update_true_north_angle(self, context):
def update_true_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -54,7 +57,7 @@ def update_true_north_angle(self, context):
self.is_changing_angle = False
def update_true_north_vector(self, context):
def update_true_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -67,7 +70,7 @@ def update_true_north_vector(self, context):
self.is_changing_angle = False
def update_grid_north_angle(self, context):
def update_grid_north_angle(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -81,7 +84,7 @@ def update_grid_north_angle(self, context):
self.is_changing_angle = False
def update_grid_north_vector(self, context):
def update_grid_north_vector(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.is_changing_angle:
return
self.is_changing_angle = True
@@ -95,15 +98,15 @@ def update_grid_north_vector(self, context):
self.is_changing_angle = False
def update_should_visualise(self, context):
def update_should_visualise(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
if self.should_visualise:
GeoreferenceDecorator.install(bpy.context)
else:
GeoreferenceDecorator.uninstall()
def update_blender_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
def update_blender_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -123,8 +126,8 @@ def update_blender_coordinates(self, context):
props.is_updating_coordinates = False
def update_local_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
def update_local_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -147,8 +150,8 @@ def update_local_coordinates(self, context):
props.is_updating_coordinates = False
def update_map_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
def update_map_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types.Context) -> None:
props = self
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
@@ -248,3 +251,45 @@ class BIMGeoreferenceProperties(PropertyGroup):
wcs_y: StringProperty(name="WCS Y", default="0")
wcs_z: StringProperty(name="WCS Z", default="0")
wcs_rotation: StringProperty(name="WCS Rotation", default="0")
if TYPE_CHECKING:
coordinate_operation_class: str
is_changing_angle: bool
is_editing: bool
is_editing_wcs: bool
is_editing_true_north: bool
coordinate_operation: bpy.types.bpy_prop_collection_idprop[Attribute]
projected_crs: bpy.types.bpy_prop_collection_idprop[Attribute]
is_updating_coordinates: bool
blender_coordinates: str
local_coordinates: str
map_coordinates: str
should_visualise: bool
visualization_scale: float
grid_north_angle: str
x_axis_abscissa: str
x_axis_ordinate: str
x_axis_is_null: bool
host_model_origin: str
host_model_origin_si: str
host_model_project_north: str
model_origin: str
model_origin_si: str
model_project_north: str
has_blender_offset: bool
blender_offset_x: str
blender_offset_y: str
blender_offset_z: str
blender_x_axis_abscissa: str
blender_x_axis_ordinate: str
true_north_angle: str
true_north_abscissa: str
true_north_ordinate: str
wcs_x: str
wcs_y: str
wcs_z: str
wcs_rotation: str
@@ -32,7 +32,7 @@ class BIM_PT_gis(Panel):
bl_parent_id = "BIM_PT_tab_geometry"
def draw_header(self, context):
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
icon = "HIDE_OFF" if props.should_visualise else "HIDE_ON"
row.label(text="") # empty text occupies the left of the row
@@ -43,7 +43,7 @@ class BIM_PT_gis(Panel):
def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
@@ -54,7 +54,7 @@ class BIM_PT_gis(Panel):
self.draw_ui(context)
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
@@ -87,7 +87,7 @@ class BIM_PT_gis(Panel):
draw_attribute(attribute, self.layout.row())
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if tool.Ifc.get_schema() == "IFC2X3":
row = self.layout.row()
@@ -149,7 +149,7 @@ class BIM_PT_gis_true_north(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
self.props = context.scene.BIMGeoreferenceProperties
self.props = tool.Georeference.get_georeference_props()
if self.props.is_editing_true_north:
self.draw_editable_ui(context)
@@ -200,7 +200,7 @@ class BIM_PT_gis_blender(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
row = self.layout.row()
@@ -233,7 +233,7 @@ class BIM_PT_gis_wcs(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.is_editing_wcs:
self.draw_editable_ui(context)
@@ -263,7 +263,7 @@ class BIM_PT_gis_wcs(Panel):
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
row = self.layout.row(align=True)
row.label(text="World Coordinate System", icon="EMPTY_ARROWS")
@@ -293,7 +293,7 @@ class BIM_PT_gis_calculator(Panel):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
row = self.layout.row(align=True)
@@ -22,7 +22,6 @@ import ifcopenshell.api.group
import ifcopenshell.util.attribute
import bonsai.bim.helper
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
import json
@@ -129,8 +128,7 @@ class RemoveGroup(bpy.types.Operator, tool.Ifc.Operator):
group: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMGroupProperties
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
bpy.ops.bim.load_groups()
return {"FINISHED"}
+3 -3
View File
@@ -17,8 +17,8 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.group.data import GroupsData, ObjectGroupsData
@@ -34,7 +34,7 @@ class BIM_PT_groups(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not GroupsData.is_loaded:
@@ -93,7 +93,7 @@ class BIM_PT_object_groups(Panel):
def poll(cls, context):
if not context.active_object:
return False
return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
return tool.Ifc.get() and context.active_object.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not ObjectGroupsData.is_loaded:
+8 -14
View File
@@ -24,7 +24,6 @@ import ifcopenshell.util.element
import ifcopenshell.util.attribute
import bonsai.bim.helper
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
class LoadLayers(bpy.types.Operator):
@@ -33,7 +32,7 @@ class LoadLayers(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = context.scene.BIMLayerProperties
props.layers.clear()
for layer in tool.Ifc.get().by_type("IfcPresentationLayerAssignment"):
@@ -126,7 +125,7 @@ class RemovePresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = context.scene.BIMLayerProperties
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run("layer.remove_layer", self.file, **{"layer": self.file.by_id(self.layer)})
bpy.ops.bim.load_layers()
return {"FINISHED"}
@@ -142,12 +141,12 @@ class AssignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"layer.assign_layer",
self.file,
**{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
"items": [self.file.by_id(tool.Geometry.get_mesh_props(item).ifc_definition_id)],
"layer": self.file.by_id(self.layer),
},
)
@@ -164,15 +163,10 @@ class UnassignPresentationLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"layer.unassign_layer",
self.file,
**{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
"layer": self.file.by_id(self.layer),
},
)
ifc_file = tool.Ifc.get()
representation = tool.Geometry.get_data_representation(item)
assert representation
ifcopenshell.api.layer.unassign_layer(ifc_file, items=[representation], layer=ifc_file.by_id(self.layer))
return {"FINISHED"}
+2 -3
View File
@@ -17,8 +17,8 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import Panel, UIList, Mesh
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.layer.data import LayersData
@@ -34,7 +34,7 @@ class BIM_PT_layers(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not LayersData.is_loaded:
@@ -77,7 +77,6 @@ class BIM_UL_layers(UIList):
row.label(text=item.name)
if context.active_object and isinstance(context.active_object.data, Mesh):
mprops = context.active_object.data.BIMMeshProperties
if item.ifc_definition_id in LayersData.data["active_layers"]:
op = row.operator("bim.unassign_presentation_layer", text="", icon="KEYFRAME_HLT", emboss=False)
op.layer = item.ifc_definition_id
@@ -282,8 +282,9 @@ class ObjectMaterialData:
if item.is_a("IfcMaterialLayer"):
total_thickness = item.LayerThickness
unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL":
precision = bpy.context.scene.DocProperties.imperial_precision
precision = props.imperial_precision
else:
precision = None
formatted_thickness = format_distance(
@@ -29,7 +29,6 @@ import bonsai.tool as tool
import bonsai.core.style
import bonsai.core.material as core
import bonsai.bim.module.model.profile as model_profile
from bonsai.bim.ifc import IfcStore
from typing import Any, Union, TYPE_CHECKING
from bonsai.bim.module.model import wall, slab
@@ -118,7 +117,7 @@ class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
self.file,
@@ -292,7 +291,7 @@ class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"material.add_constituent",
self.file,
@@ -328,7 +327,7 @@ class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = tool.Material.get_material_props()
ifcopenshell.api.run(
"material.add_profile",
@@ -387,7 +386,7 @@ class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
material_set = self.file.by_id(self.material_set)
ifcopenshell.api.run(
"material.reorder_set_item",
@@ -443,7 +442,7 @@ class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"material.add_list_item",
self.file,
@@ -463,7 +462,7 @@ class RemoveListItem(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"material.remove_list_item",
self.file,
@@ -557,7 +556,7 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator):
material_set_usage: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
active_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = active_obj.BIMObjectMaterialProperties
element = tool.Ifc.get_entity(active_obj)
@@ -663,7 +662,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
material_set_item: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.mprops = tool.Material.get_material_props()
self.props = obj.BIMObjectMaterialProperties
@@ -707,7 +706,7 @@ class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = obj.BIMObjectMaterialProperties
mprops = tool.Material.get_material_props()
element = tool.Ifc.get_entity(obj)
@@ -22,7 +22,6 @@ from ifcopenshell.util.doc import get_entity_doc
import bonsai.tool as tool
from bonsai.bim.module.material.data import MaterialsData, ObjectMaterialData
from bonsai.bim.module.profile.data import ProfileData
from bonsai.bim.ifc import IfcStore
from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
+6 -6
View File
@@ -21,7 +21,6 @@ import bonsai.bim.helper
import bonsai.tool as tool
import bpy
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import draw_attributes
from bonsai.bim.helper import prop_with_search
from bonsai.bim.module.material.data import MaterialsData, ObjectMaterialData
@@ -43,7 +42,7 @@ class BIM_PT_materials(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not MaterialsData.is_loaded:
@@ -143,9 +142,9 @@ class BIM_PT_object_material(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
if not hasattr(IfcStore.get_file().by_id(props.ifc_definition_id), "HasAssociations"):
if not hasattr(tool.Ifc.get().by_id(props.ifc_definition_id), "HasAssociations"):
return False
return True
@@ -153,7 +152,7 @@ class BIM_PT_object_material(Panel):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties
self.mprops = tool.Material.get_material_props()
@@ -351,9 +350,10 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["total_thickness"]:
total_thickness = ObjectMaterialData.data["total_thickness"]
unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props()
if unit_system == "IMPERIAL":
precision = bpy.context.scene.DocProperties.imperial_precision
precision = props.imperial_precision
else:
precision = None
formatted_thickness = format_distance(
@@ -299,7 +299,7 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.select_and_activate_single_object(context, curve)
def get_absolute_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
props = tool.Georeference.get_georeference_props()
if props.has_blender_offset:
matrix = np.array(
ifcopenshell.util.geolocation.global2local(
+4 -7
View File
@@ -26,7 +26,7 @@ from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
import bonsai.tool as tool
from math import degrees
from natsort import natsorted
from typing import Union
from typing import Union, Optional
def refresh():
@@ -45,17 +45,14 @@ class AuthoringData:
data = {}
type_thumbnails = {}
types_per_page = 9
ifc_element_type = None
is_loaded = False
@classmethod
def load(cls, ifc_element_type=None):
def load(cls, ifc_element_type: Optional[str] = None):
cls.is_loaded = True
cls.props = tool.Model.get_model_props()
if ifc_element_type:
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
cls.data["default_container"] = cls.default_container()
cls.data["ifc_element_type"] = cls.ifc_element_type
cls.data["ifc_element_type"] = ifc_element_type
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["ifc_class_current"] = cls.ifc_class_current()
# Make sure .ifc_classes() was run before next lines
@@ -97,7 +94,7 @@ class AuthoringData:
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
props = tool.Spatial.get_spatial_props()
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
@@ -20,7 +20,6 @@ import bpy
import ifcopenshell
import ifcopenshell.api
from bonsai.bim.module.model import product, wall, slab, profile, opening, task
from bonsai.bim.ifc import IfcStore
from bpy.app.handlers import persistent
+19 -15
View File
@@ -38,7 +38,6 @@ import bonsai.tool as tool
import bonsai.core.geometry
import bonsai.bim.import_ifc as import_ifc
from collections import defaultdict
from bonsai.bim.ifc import IfcStore
from math import pi, radians
from mathutils import Vector, Matrix
from bpy.types import Operator
@@ -236,9 +235,9 @@ class FilledOpeningGenerator:
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
for voided_element in voided_elements:
voided_obj = tool.Ifc.get_object(voided_element)
if not voided_obj.data:
representation = tool.Geometry.get_active_representation(voided_obj)
if not representation:
continue
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -465,10 +464,13 @@ class AddBoolean(Operator, tool.Ifc.Operator):
self.report({"INFO"}, "At least two representation items must be selected to add a boolean.")
return {"CANCELLED"}
props = context.scene.BIMBooleanProperties
props = tool.Feature.get_boolean_props()
first_item = tool.Ifc.get().by_id(first_obj.data.BIMMeshProperties.ifc_definition_id)
second_items = [tool.Ifc.get().by_id(o.data.BIMMeshProperties.ifc_definition_id) for o in second_objs]
first_item = tool.Geometry.get_active_representation(first_obj)
assert first_item
second_items = [
representation for o in second_objs if (representation := tool.Geometry.get_active_representation(o))
]
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
rep_obj = tool.Geometry.get_geometry_props().representation_obj
@@ -664,6 +666,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
def edit_openings(
self, building_objs: set[bpy.types.Object], opening_elements: set[ifcopenshell.entity_instance]
) -> None:
props = tool.Geometry.get_geometry_props()
objects_to_remove: set[bpy.types.Object] = set()
for opening_element in opening_elements:
opening_obj = tool.Ifc.get_object(opening_element)
@@ -690,8 +693,8 @@ class EditOpenings(Operator, tool.Ifc.Operator):
self.get_all_building_objects_of_similar_openings(opening_element)
) # NB this has nothing to do with clone similar_opening
tool.Ifc.unlink(element=opening_element)
if bpy.context.scene.BIMGeometryProperties.representation_obj == opening_obj:
bpy.context.scene.BIMGeometryProperties.representation_obj = None
if props.representation_obj == opening_obj:
props.representation_obj = None
objects_to_remove.add(opening_obj)
tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True)
@@ -817,11 +820,11 @@ class RemoveBoolean(Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.BIMBooleanProperties
props = tool.Feature.get_boolean_props()
return props.active_boolean
def _execute(self, context):
props = context.scene.BIMBooleanProperties
props = tool.Feature.get_boolean_props()
ifcopenshell.api.geometry.remove_boolean(
tool.Ifc.get(), tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)
)
@@ -841,7 +844,7 @@ class SelectBoolean(Operator):
@classmethod
def poll(cls, context):
props = context.scene.BIMBooleanProperties
props = tool.Feature.get_boolean_props()
return props.active_boolean
def invoke(self, context, event):
@@ -850,9 +853,9 @@ class SelectBoolean(Operator):
return self.execute(context)
def execute(self, context):
props = context.scene.BIMBooleanProperties
props = tool.Feature.get_boolean_props()
queue = [tool.Ifc.get().by_id(props.active_boolean.ifc_definition_id)]
items = {i.ifc_definition_id: i.obj for i in context.scene.BIMGeometryProperties.item_objs}
items = {i.ifc_definition_id: i.obj for i in tool.Geometry.get_geometry_props().item_objs}
while queue:
item = queue.pop()
if item.is_a("IfcBooleanResult"):
@@ -911,11 +914,12 @@ class DecorationsHandler:
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
gprops = tool.Geometry.get_geometry_props()
for opening in props.openings:
obj = opening.obj
if context.scene.BIMGeometryProperties.representation_obj == obj:
if gprops.representation_obj == obj:
# We are editing the representation of the opening :
for item in context.scene.BIMGeometryProperties.item_objs:
for item in gprops.item_objs:
if item.obj.mode == "EDIT":
obj = item.obj
break
+85 -67
View File
@@ -35,7 +35,6 @@ import bonsai.core.root
import bonsai.core.geometry
import bonsai.core.model as core
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from math import pi, sin, cos, degrees, tan, radians
from mathutils import Vector, Matrix, Quaternion
from bonsai.bim.module.model.opening import FilledOpeningGenerator
@@ -430,71 +429,90 @@ def get_horizontal_profile_preview_data(context, relating_type):
case "9":
grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
# Create profile curve
scale_mat = Matrix.Scale(-1, 4, (1.0, 0.0, 0.0))
grouped_verts = [scale_mat @ Vector(v) for v in grouped_verts]
profile_curve = bpy.data.curves.new("Profile", type="CURVE")
profile_curve.dimensions = "2D"
profile_curve.splines.new("POLY")
profile_curve.splines[0].points.add(len(grouped_verts))
for i, point in enumerate(profile_curve.splines[0].points):
if i == len(grouped_verts): # Close curve
point.co = Vector((*grouped_verts[0], 0))
continue
point.co = Vector((*grouped_verts[i], 0))
profile_obj = bpy.data.objects.new("Profile", profile_curve)
# Create path curve with profile object as bevel
path_curve = bpy.data.curves.new("Polyline", type="CURVE")
path_curve.dimensions = "2D"
path_curve.splines.new("POLY")
path_curve.splines[0].points.add(len(polyline_verts) - 1)
for i, point in enumerate(path_curve.splines[0].points):
point.co = Vector((*polyline_verts[i], 0))
path_curve.splines[0].use_smooth = False
path_curve.bevel_mode = "OBJECT"
path_curve.bevel_object = profile_obj
# Convert path curve to mesh
# This operation throws a warning when done during gpu drawing, so it was removed from the decorator file to be handled here
path_obj = bpy.data.objects.new("Preview", path_curve)
context.scene.collection.objects.link(path_obj)
bpy.context.view_layer.objects.active = path_obj
dg = context.evaluated_depsgraph_get()
path_obj = path_obj.evaluated_get(dg)
me = path_obj.to_mesh()
# Create bmesh from path mesh
bm = bmesh.new()
new_verts = [bm.verts.new(v.co) for v in me.vertices]
index = [[v for v in edge.vertices] for edge in me.edges]
new_edges = [bm.edges.new((new_verts[i[0]], new_verts[i[1]])) for i in index]
for face in me.polygons:
verts = [new_verts[i] for i in face.vertices]
bm.faces.new(verts)
bm.verts.index_update()
bm.edges.index_update()
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
bpy.data.objects.remove(bpy.data.objects[path_obj.name], do_unlink=True)
bpy.data.objects.remove(bpy.data.objects[profile_obj.name], do_unlink=True)
try:
bpy.data.curves.remove(profile_obj.data, do_unlink=True)
except:
pass
try:
bpy.data.curves.remove(path_obj.data, do_unlink=True)
except:
pass
data = {}
data["verts"] = [tuple(v.co) for v in bm.verts]
data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges]
data["verts"] = []
data["edges"] = []
data["tris"] = []
grouped_verts = [(v) for v in grouped_verts]
all_bm = bmesh.new()
for i in range(len(polyline_verts) - 1):
mesh = bpy.data.meshes.new("TempMesh")
# Create the initial mesh from the profile verts
bm = create_bmesh_from_vertices(grouped_verts, is_closed=True)
bm.verts.ensure_lookup_table()
# Creates the clipping plane formed by two segments.
# The first one is for the profile start, based on the current and previous segment of the polyline.
# The second is for the profile end, based on the current and the next segment.
if i == 0:
d = (polyline_verts[i + 1] - polyline_verts[i]).normalized()
clip_start = d
else:
d1 = (polyline_verts[i] - polyline_verts[i - 1]).normalized()
d2 = (polyline_verts[i] - polyline_verts[i + 1]).normalized()
clip_start = (d1 - d2).normalized()
if i == len(polyline_verts) - 2:
d = (polyline_verts[i + 1] - polyline_verts[i]).normalized()
clip_end = d
else:
d1 = (polyline_verts[i + 1] - polyline_verts[i]).normalized()
d2 = (polyline_verts[i + 1] - polyline_verts[i + 2]).normalized()
clip_end = (d1 - d2).normalized()
# Rotates the profile face to the right direction
direction = polyline_verts[i + 1] - polyline_verts[i]
position = polyline_verts[i]
rotation_matrix = direction.to_track_quat("Z", "Y").to_matrix().to_4x4()
bmesh.ops.transform(bm, verts=bm.verts, matrix=rotation_matrix)
bmesh.ops.translate(bm, verts=bm.verts, vec=position)
bmesh.ops.translate(bm, verts=bm.verts, vec=-direction)
# Extrude and move the new face
last_face = bmesh.ops.extrude_face_region(bm, geom=bm.edges[:] + bm.faces[:])
new_verts = [e for e in last_face["geom"] if isinstance(e, bmesh.types.BMVert)]
bmesh.ops.translate(bm, verts=new_verts, vec=direction * 3)
# Apply the cutting planes
cut = bmesh.ops.bisect_plane(
bm,
geom=bm.verts[:] + bm.edges[:] + bm.faces[:],
plane_co=polyline_verts[i],
plane_no=clip_start,
clear_inner=True,
)
bm.verts.index_update()
bm.edges.index_update()
cut = bmesh.ops.bisect_plane(
bm,
geom=bm.verts[:] + bm.edges[:] + bm.faces[:],
plane_co=polyline_verts[i + 1],
plane_no=clip_end,
clear_outer=True,
)
bm.to_mesh(mesh)
bm.free()
mesh.update()
all_bm.from_mesh(mesh)
bpy.data.meshes.remove(bpy.data.meshes["TempMesh"])
# It's necessary to add the mesh to an object to get the expected result.
mesh = bpy.data.meshes.new("TempMesh2")
all_bm.to_mesh(mesh)
all_bm.free()
obj = bpy.data.objects.new("TempObj", mesh)
bm = bmesh.new()
bm.from_mesh(obj.data)
bpy.data.meshes.remove(bpy.data.meshes["TempMesh2"])
verts = [tuple(v.co) for v in bm.verts]
edges = [[v.index for v in e.verts] for e in bm.edges]
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
data["verts"] = verts
data["edges"] = edges
data["tris"] = tris
bm.free()
return data
@@ -598,7 +616,7 @@ class PolylineOperator:
self.report({"WARNING"}, "The number typed is not valid.")
return is_valid
else:
if self.input_type in {"X", "Y"}:
if self.input_type in {"X", "Y", "Z"}:
tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state)
elif self.input_type in {"D", "A"}:
tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state)
@@ -633,21 +651,21 @@ class PolylineOperator:
if x:
if event.shift and event.value == "PRESS" and event.type == "X":
self.tool_state.use_default_container = False
self.tool_state.plane_method = "YZ"
self.tool_state.plane_method = "YZ" if self.tool_state.plane_method != "YZ" else None
self.tool_state.axis_method = None
tool.Blender.update_viewport()
if y:
if event.shift and event.value == "PRESS" and event.type == "Y":
self.tool_state.use_default_container = False
self.tool_state.plane_method = "XZ"
self.tool_state.plane_method = "XZ" if self.tool_state.plane_method != "XZ" else None
self.tool_state.axis_method = None
tool.Blender.update_viewport()
if z:
if event.shift and event.value == "PRESS" and event.type == "Z":
self.tool_state.use_default_container = False
self.tool_state.plane_method = "XY"
self.tool_state.plane_method = "XY" if self.tool_state.plane_method != "XY" else None
self.tool_state.axis_method = None
tool.Blender.update_viewport()
@@ -58,7 +58,8 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
def execute(self, context):
obj = bpy.data.objects.new("TYPEX", None)
context.scene.collection.objects.link(obj)
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
rprops = tool.Root.get_root_props()
rprops.ifc_product = "IfcElementType"
tool.Blender.select_and_activate_single_object(context, obj)
return {"FINISHED"}
@@ -71,7 +72,7 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
ifc_element_type: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
props.ifc_product = "IfcElementType"
props.ifc_class = self.ifc_element_type
if self.ifc_element_type == "IfcWallType":
@@ -363,7 +364,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
)
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
rprops = context.scene.BIMRootProperties
rprops = tool.Root.get_root_props()
ifc_context = None
if get_enum_items(rprops, "contexts", context):
ifc_context = int(rprops.contexts or "0") or None
@@ -757,7 +758,7 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
elements.append(element)
for element in elements:
obj = IfcStore.get_element(element.id())
obj = tool.Ifc.get_object_by_identifier(element.id())
if not obj:
continue
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
+30 -19
View File
@@ -22,6 +22,7 @@ import bmesh
import mathutils.geometry
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.geometry
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
@@ -47,6 +48,7 @@ class DumbProfileGenerator:
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
def generate(self, insertion_type="CURSOR"):
self.insertion_type = insertion_type
self.file = tool.Ifc.get()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
material = ifcopenshell.util.element.get_material(self.relating_type)
@@ -69,9 +71,9 @@ class DumbProfileGenerator:
self.rotation = 0
self.location = Vector((0, 0, 0))
self.cardinal_point = int(props.cardinal_point)
if insertion_type == "POLYLINE":
if self.insertion_type == "POLYLINE":
return self.derive_from_polyline()
elif insertion_type == "CURSOR":
elif self.insertion_type == "CURSOR":
return self.derive_from_cursor()
def derive_from_polyline(self) -> tuple[list[Union[dict[str, Any], None]], bool]:
@@ -106,13 +108,16 @@ class DumbProfileGenerator:
matrix_world = Matrix()
if self.relating_type.is_a() not in ("IfcColumnType", "IfcPileType"):
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
matrix_world = Matrix.Rotation(self.rotation, 4, "Z") @ matrix_world
if self.insertion_type not in {"POLYLINE"}:
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
matrix_world = Matrix.Rotation(self.rotation, 4, "Z") @ matrix_world
else:
rotation_matrix = self.direction.to_track_quat("Z", "Y")
matrix_world = rotation_matrix.to_matrix().to_4x4() @ matrix_world
matrix_world.translation = self.location
if self.container_obj:
if self.insertion_type not in {"POLYLINE"} and self.container_obj:
matrix_world.translation.z = self.container_obj.location.z
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
@@ -170,19 +175,19 @@ class DumbProfileGenerator:
return obj
def create_profile_from_2_points(self, coords, should_round=False) -> Union[dict[str, Any], None]:
direction = coords[1] - coords[0]
length = direction.length
self.direction = coords[1] - coords[0]
length = self.direction.length
if round(length, 4) < 0.1:
return
data = {"coords": coords}
self.depth = length
self.rotation = atan2(direction[1], direction[0])
self.rotation = atan2(self.direction[1], self.direction[0])
if should_round:
# Round to nearest 50mm (yes, metric for now)
self.length = 0.05 * round(length / 0.05)
# Round to nearest 5 degrees
nearest_degree = (math.pi / 180) * 5
nearest_degree = (pi / 180) * 5
self.rotation = nearest_degree * round(self.rotation / nearest_degree)
self.location = coords[0]
data["obj"] = self.create_profile()
@@ -265,7 +270,7 @@ class DumbProfileRegenerator:
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
if not obj or not tool.Geometry.get_active_representation(obj):
return
DumbProfileRecalculator().recalculate([obj])
@@ -501,7 +506,9 @@ class DumbProfileJoiner:
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis
)
def get_placement_axes(body_representation):
def get_placement_axes(
body_representation: Union[ifcopenshell.entity_instance, None],
) -> Union[tuple[tuple[float, float, float], tuple[float, float, float]], tuple[None, None]]:
if not body_representation:
return None, None
extrusion = tool.Model.get_extrusion(body_representation)
@@ -513,8 +520,7 @@ class DumbProfileJoiner:
return ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0))
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
new_body = ifcopenshell.api.run(
"geometry.add_profile_representation",
new_body = ifcopenshell.api.geometry.add_profile_representation(
tool.Ifc.get(),
context=self.body_context,
profile=self.profile,
@@ -527,8 +533,9 @@ class DumbProfileJoiner:
if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id())
obj.data.name = f"{self.body_context.id()}/{new_body.id()}"
assert isinstance(mesh := obj.data, bpy.types.Mesh)
tool.Ifc.link(new_body, mesh)
mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else:
ifcopenshell.api.run(
@@ -1119,6 +1126,8 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator, tool.Ifc.Operato
def __init__(self):
super().__init__()
self.input_ui = tool.Polyline.create_input_ui(init_z=True)
self.input_options = ["D", "A", "X", "Y", "Z"]
self.relating_type = None
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
@@ -1163,7 +1172,9 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator, tool.Ifc.Operato
self.handle_mouse_move(context, event, should_round=True)
self.choose_axis(event)
self.choose_axis(event, z=True)
self.choose_plane(event)
self.handle_snap_selection(context, event)
@@ -1199,6 +1210,6 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator, tool.Ifc.Operato
def _invoke(self, context, event):
super().invoke(context, event)
ProductDecorator.install(context)
self.tool_state.use_default_container = True
self.tool_state.plane_method = "XY"
self.tool_state.use_default_container = False
self.tool_state.plane_method = None
return {"RUNNING_MODAL"}
@@ -60,6 +60,8 @@ def get_materials(self, context):
def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["ifc_class_current"] = self.ifc_class
AuthoringData.data["type_elements"] = AuthoringData.type_elements()
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
+4 -2
View File
@@ -259,7 +259,7 @@ class DumbSlabPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
if not obj or not tool.Geometry.get_active_representation(obj):
return
material = ifcopenshell.util.element.get_material(related_object)
@@ -885,11 +885,13 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
return {"FINISHED"}
slab = DumbSlabGenerator(self.relating_type).generate("POLYLINE")
if not slab:
return
model_props = tool.Model.get_model_props()
direction_sense = model_props.direction_sense
offset = model_props.offset
model = IfcStore.get_file()
model = tool.Ifc.get()
element = tool.Ifc.get_entity(slab)
material = ifcopenshell.util.element.get_material(element)
material_set_usage = model.by_id(material.id())
+2 -3
View File
@@ -117,9 +117,8 @@ class LaunchTypeManager(bpy.types.Operator):
op.ifc_product = "IfcElementType"
op.ifc_class = AuthoringData.data["ifc_element_type"] or props.ifc_class or ""
if AuthoringData.data["total_types"]:
row = self.layout.row(align=True)
row.prop(props, "search_name", icon="FILTER", text="")
row = self.layout.row(align=True)
row.prop(props, "search_name", icon="FILTER", text="")
columns = self.layout.column_flow(columns=3)
if AuthoringData.data["total_pages"] > 0:
+14 -8
View File
@@ -377,7 +377,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
walls, is_polyline_closed = DumbWallGenerator(self.relating_type).generate("POLYLINE")
for wall in walls:
model = IfcStore.get_file()
model = tool.Ifc.get()
element = tool.Ifc.get_entity(wall["obj"])
material = ifcopenshell.util.element.get_material(element)
material_set_usage = model.by_id(material.id())
@@ -598,7 +598,7 @@ class DumbWallGenerator:
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
def generate(self, insertion_type="CURSOR"):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.layers = tool.Model.get_material_layer_parameters(self.relating_type)
if not self.layers["thickness"]:
return
@@ -908,7 +908,7 @@ class DumbWallPlaner:
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
if not obj or not tool.Geometry.get_active_representation(obj):
return
material = ifcopenshell.util.element.get_material(related_object)
@@ -1331,7 +1331,9 @@ class DumbWallJoiner:
axis = body = tool.Model.get_wall_axis(obj)["reference"]
self.axis = copy.deepcopy(axis)
self.body = copy.deepcopy(body)
extrusion_data = self.get_extrusion_data(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id))
representation = tool.Geometry.get_active_representation(obj)
assert representation
extrusion_data = self.get_extrusion_data(representation)
height = extrusion_data["height"]
x_angle = extrusion_data["x_angle"]
self.clippings = []
@@ -1410,8 +1412,9 @@ class DumbWallJoiner:
if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id())
obj.data.name = f"{self.body_context.id()}/{new_body.id()}"
assert isinstance(mesh := obj.data, bpy.types.Mesh)
tool.Ifc.link(new_body, mesh)
mesh.name = tool.Loader.get_mesh_name(new_body)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
else:
ifcopenshell.api.run(
@@ -1708,10 +1711,11 @@ class DumbWallJoiner:
return True
def clip(self, wall1, slab2):
def clip(self, wall1: bpy.types.Object, slab2: bpy.types.Object) -> float:
"""returns height of the clipped wall, adds clipping plane to `clippings`"""
element1 = tool.Ifc.get_entity(wall1)
element2 = tool.Ifc.get_entity(slab2)
assert element1 and element2
layers1 = tool.Model.get_material_layer_parameters(element1)
axis1 = tool.Model.get_wall_axis(wall1, layers1)
@@ -1719,7 +1723,9 @@ class DumbWallJoiner:
bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()]
bases = [Vector((v[0], v[1], wall1.matrix_world.translation.z)) for v in bases] # add wall Z location
extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id))
representation = tool.Geometry.get_active_representation(wall1)
assert representation
extrusion = self.get_extrusion_data(representation)
wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
slab_pt = slab2.matrix_world @ Vector((0, 0, 0))
+13 -16
View File
@@ -96,14 +96,16 @@ class BimTool(WorkSpaceTool):
def draw_settings(
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ws_tool: bpy.types.WorkSpaceTool
) -> None:
if context.scene.BIMGeometryProperties.mode == "ITEM":
props = tool.Geometry.get_geometry_props()
ifc_element_type = None if cls.ifc_element_type == "all" else cls.ifc_element_type
if props.mode == "ITEM":
EditItemUI.draw(context, layout)
elif (
active_ifc_object := (context.active_object and tool.Ifc.get_entity(context.active_object))
) and context.selected_objects:
EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
EditObjectUI.draw(context, layout, ifc_element_type=ifc_element_type)
else:
CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
CreateObjectUI.draw(context, layout, ifc_element_type=ifc_element_type)
class WallTool(BimTool):
@@ -430,7 +432,7 @@ class EditItemUI:
obj = context.active_object
assert obj
mesh_props = obj.data.BIMMeshProperties
mesh_props = tool.Geometry.get_mesh_props(obj.data)
if AuthoringData.data["is_representation_item_swept_solid"]:
# TODO: support EndSweptArea for IfcRevolvedAreaSolidTapered,
# will need to add second attribute for this.
@@ -440,10 +442,10 @@ class EditItemUI:
op = row.operator("bim.name_profile", text="", icon="TAG")
op.extrusion_item_obj = obj.name
for item_attribute in obj.data.BIMMeshProperties.item_attributes:
for item_attribute in mesh_props.item_attributes:
row = cls.layout.row()
draw_attribute(item_attribute, cls.layout)
if len(obj.data.BIMMeshProperties.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]:
if len(mesh_props.item_attributes) or AuthoringData.data["is_representation_item_swept_solid"]:
row = cls.layout.row()
row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="")
@@ -499,9 +501,7 @@ class CreateObjectUI:
layout: bpy.types.UILayout
@classmethod
def draw(
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Optional[str] = None
) -> None:
def draw(cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Union[str, None]) -> None:
cls.layout = layout
cls.props = tool.Model.get_model_props()
@@ -515,15 +515,13 @@ class CreateObjectUI:
if not AuthoringData.is_loaded:
AuthoringData.load(ifc_element_type)
elif ifc_element_type == "all" and AuthoringData.data["ifc_element_type"] is not None:
AuthoringData.load("all")
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
AuthoringData.load(ifc_element_type)
if ifc_element_type and context.region.type == "TOOL_HEADER":
if context.region.type == "TOOL_HEADER":
tool_name = (
"Multi Object Tool"
if ifc_element_type == "all"
if ifc_element_type is None
else format_ifc_camel_case(ifc_element_type.removesuffix("Type")) + " Tool"
)
cls.layout.label(text=tool_name, icon="TOOL_SETTINGS")
@@ -746,8 +744,6 @@ class EditObjectUI:
if not AuthoringData.is_loaded:
AuthoringData.load(ifc_element_type)
elif ifc_element_type == "all" and AuthoringData.data["ifc_element_type"] is not None:
AuthoringData.load("all")
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
AuthoringData.load(ifc_element_type)
@@ -1136,7 +1132,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "z")
def hotkey_S_A(self):
if bpy.context.scene.BIMGeometryProperties.mode == "ITEM":
gprops = tool.Geometry.get_geometry_props()
if gprops.mode == "ITEM":
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
return
@@ -21,7 +21,6 @@ import ifcopenshell
import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.nest as core
from bonsai.bim.ifc import IfcStore
class BIM_OT_nest_assign_object(bpy.types.Operator, tool.Ifc.Operator):
@@ -113,7 +112,7 @@ class BIM_OT_select_components(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
obj = bpy.data.objects.get(self.obj) or context.active_object
components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(obj))
component_objs = set(tool.Ifc.get_object(c) for c in components)
@@ -132,7 +131,7 @@ class BIM_OT_select_nest(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
obj = bpy.data.objects.get(self.obj) or context.active_object
nest = ifcopenshell.util.element.get_nest(tool.Ifc.get_entity(obj))
nest_obj = tool.Ifc.get_object(nest)
+3 -3
View File
@@ -16,9 +16,9 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.tool as tool
from bpy.types import Panel
from bonsai.bim.module.nest.data import NestData
from bonsai.bim.ifc import IfcStore
class BIM_PT_nest(Panel):
@@ -37,9 +37,9 @@ class BIM_PT_nest(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False
return True
+3 -3
View File
@@ -94,7 +94,7 @@ class ProfileData:
obj = bpy.context.active_object
return (
obj
and obj.data
and hasattr(obj.data, "BIMMeshProperties")
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
and (data := obj.data)
and isinstance(data, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(data).subshape_type == "PROFILE"
)
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell.api
import ifcopenshell.api.profile
import ifcopenshell.util.element
import bonsai.bim.helper
import bonsai.tool as tool
@@ -174,12 +175,12 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
props.object_to_profile = None
if not indices:
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
profile = ifcopenshell.api.profile.add_arbitrary_profile(tool.Ifc.get(), profile=points)
else:
if "inner_curves" not in indices:
points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]]
points.append(points[0])
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
profile = ifcopenshell.api.profile.add_arbitrary_profile(tool.Ifc.get(), profile=points)
else:
outer_points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]]
outer_points.append(outer_points[0])
@@ -189,8 +190,7 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
]
for curve in inner_points:
curve.append(curve[0])
profile = ifcopenshell.api.run(
"profile.add_arbitrary_profile_with_voids",
profile = ifcopenshell.api.profile.add_arbitrary_profile_with_voids(
tool.Ifc.get(),
outer_profile=outer_points,
inner_profiles=inner_points,
@@ -248,7 +248,12 @@ class EnableEditingArbitraryProfile(bpy.types.Operator):
def disable_editing_arbitrary_profile(context):
obj = context.active_object
if obj and obj.type == "MESH" and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
if (
obj
and (mesh := obj.data)
and isinstance(mesh, bpy.types.Mesh)
and tool.Geometry.get_mesh_props(mesh).subshape_type == "PROFILE"
):
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
profile_mesh = obj.data
@@ -21,7 +21,6 @@ import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.module.profile.data import ProfileData
from bpy.types import PropertyGroup
@@ -31,7 +31,8 @@ from typing import Union
@persistent
def toggle_decorations_on_load(*args):
if bpy.context.scene.BIMProjectProperties.clipping_planes:
props = tool.Project.get_project_props()
if props.clipping_planes:
ClippingPlaneDecorator.install(bpy.context)
else:
ClippingPlaneDecorator.uninstall()
@@ -99,7 +100,7 @@ class ProjectDecorator:
selected_edges = []
selected_tris = []
props = context.scene.BIMProjectProperties
props = tool.Project.get_project_props()
try:
obj = props.queried_obj
selected_vertices = obj["selected_vertices"]
@@ -171,7 +172,8 @@ class ClippingPlaneDecorator:
unselected_edges = []
unselected_tris = []
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
props = tool.Project.get_project_props()
for clipping_plane in props.clipping_planes:
obj = clipping_plane.obj
if not obj or not obj.data:
continue
@@ -18,6 +18,7 @@
import bpy
import bonsai.tool as tool
from bpy.types import GizmoGroup
from mathutils import Matrix
@@ -32,11 +33,12 @@ class ClippingPlane(GizmoGroup):
@classmethod
def poll(cls, context):
obj = context.object
props = tool.Project.get_project_props()
return (
context.selected_objects
and obj
and obj.name.startswith("ClippingPlane")
and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes]
and obj in [sp.obj for sp in props.clipping_planes]
)
def setup(self, context):
+139 -111
View File
@@ -75,35 +75,37 @@ class NewProject(bpy.types.Operator):
def execute(self, context):
bpy.ops.wm.read_homefile()
pprops = tool.Project.get_project_props()
bim_props = tool.Blender.get_bim_props()
if self.preset == "metric_m":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "METERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0"
bim_props.area_unit = "SQUARE_METRE"
bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "0"
elif self.preset == "metric_mm":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0"
bim_props.area_unit = "SQUARE_METRE"
bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "0"
elif self.preset == "imperial_ft":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "FEET"
bpy.context.scene.BIMProperties.area_unit = "square foot"
bpy.context.scene.BIMProperties.volume_unit = "cubic foot"
bpy.context.scene.BIMProjectProperties.template_file = "0"
bim_props.area_unit = "square foot"
bim_props.volume_unit = "cubic foot"
pprops.template_file = "0"
elif self.preset == "demo":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
pprops.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Template.ifc"
bim_props.area_unit = "SQUARE_METRE"
bim_props.volume_unit = "CUBIC_METRE"
pprops.template_file = "IFC4 Demo Template.ifc"
if self.preset != "wizard":
bpy.ops.bim.create_project()
@@ -126,7 +128,7 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
props = context.scene.BIMProjectProperties
props = tool.Project.get_project_props()
template = None if props.template_file == "0" else props.template_file
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
@@ -236,7 +238,7 @@ class RefreshLibrary(bpy.types.Operator):
elements.update(library_file.by_type(importable_type))
rels = tool.Project.get_project_library_rels(library_file)
elements = {e for e in elements if not tool.Project.is_element_assigned_to_project_library(e, rels)}
self.props.add_library_project_library("Unassigned", len(elements), 0)
self.props.add_library_project_library("Unassigned", len(elements), 0, False)
ifc_project = library_file.by_type("IfcProject")[0]
hierarchy = tool.Project.get_project_hierarchy(library_file)
@@ -304,13 +306,14 @@ class ChangeLibraryElement(bpy.types.Operator):
if self.breadcrumb_type == "LIBRARY":
hierarchy = tool.Project.get_project_hierarchy(library_file)
assert active_project_library is not None
if active_project_library == "NO_LIBRARY" or not hierarchy[active_project_library]:
for appendable_type in sorted(tool.Project.get_appendable_asset_types()):
elements = library_file.by_type(appendable_type)
if elements := filter_elements(elements):
self.props.add_library_asset_class(appendable_type, len(elements))
else:
if active_project_library != "NO_LIBRARY" and hierarchy[active_project_library]:
tool.Project.load_project_libraries_to_ui(active_project_library, hierarchy)
for appendable_type in sorted(tool.Project.get_appendable_asset_types()):
elements = library_file.by_type(appendable_type)
if elements := filter_elements(elements):
self.props.add_library_asset_class(appendable_type, len(elements))
else: # breadcrumb_type CLASS.
elements = self.library_file.by_type(self.element_name)
elements = list(filter_elements(elements))
@@ -501,10 +504,10 @@ class AppendEntireLibrary(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.library = IfcStore.library_file
query = ", ".join(tool.Project.get_appendable_asset_types())
@@ -521,10 +524,10 @@ class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
self.library = IfcStore.library_file
for element in ifcopenshell.util.selector.filter_elements(self.library, self.query):
@@ -554,7 +557,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
poll = bool(IfcStore.get_file())
poll = bool(tool.Ifc.get())
if not poll:
cls.poll_message_set("Please create or load a project first.")
return poll
@@ -591,7 +594,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
self.import_presentation_style_from_ifc(element, context)
try:
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
props = tool.Project.get_project_props()
props.library_elements[self.prop_index].is_appended = True
except:
# TODO Remove this terrible code when I refactor this into the core
pass
@@ -599,7 +603,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def import_material_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
@@ -617,7 +621,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
ifc_importer.create_style(style)
def import_product_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
@@ -630,7 +634,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
ifc_importer.place_objects_in_collections()
def import_type_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
@@ -645,7 +649,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
def import_materials(self, element: ifcopenshell.entity_instance, ifc_importer: import_ifc.IfcImporter) -> None:
for material in ifcopenshell.util.element.get_materials(element):
if IfcStore.get_element(material.id()):
if tool.Ifc.get_object_by_identifier(material.id()):
continue
self.import_material_styles(material, ifc_importer)
@@ -659,7 +663,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
if not element.is_a("IfcRepresentationItem") or not element.StyledByItem:
continue
for element2 in self.file.traverse(element.StyledByItem[0]):
if element2.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element2.id()):
if element2.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element2.id()):
ifc_importer.create_style(element2)
def import_material_styles(
@@ -670,7 +674,7 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
if not material.HasRepresentation:
return
for element in self.file.traverse(material.HasRepresentation[0]):
if element.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element.id()):
if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()):
ifc_importer.create_style(element)
@@ -767,14 +771,14 @@ class EnableEditingHeader(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMProjectProperties
self.file = tool.Ifc.get()
props = tool.Project.get_project_props()
props.is_editing = True
mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description)
mvd = "".join(tool.Ifc.get().wrapped_data.header.file_description.description)
if "[" in mvd:
props.mvd = mvd.split("[")[1][0:-1]
else:
@@ -804,7 +808,7 @@ class EditHeader(bpy.types.Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def execute(self, context):
IfcStore.begin_transaction(self)
@@ -817,8 +821,8 @@ class EditHeader(bpy.types.Operator):
return result
def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMProjectProperties
self.file = tool.Ifc.get()
props = tool.Project.get_project_props()
props.is_editing = True
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
@@ -829,7 +833,7 @@ class EditHeader(bpy.types.Operator):
return {"FINISHED"}
def record_state(self):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
return {
"description": self.file.wrapped_data.header.file_description.description,
"author": self.file.wrapped_data.header.file_name.author,
@@ -838,14 +842,14 @@ class EditHeader(bpy.types.Operator):
}
def rollback(self, data):
file = IfcStore.get_file()
file = tool.Ifc.get()
file.wrapped_data.header.file_description.description = data["old"]["description"]
file.wrapped_data.header.file_name.author = data["old"]["author"]
file.wrapped_data.header.file_name.organization = data["old"]["organisation"]
file.wrapped_data.header.file_name.authorization = data["old"]["authorisation"]
def commit(self, data):
file = IfcStore.get_file()
file = tool.Ifc.get()
file.wrapped_data.header.file_description.description = data["new"]["description"]
file.wrapped_data.header.file_name.author = data["new"]["author"]
file.wrapped_data.header.file_name.organization = data["new"]["organisation"]
@@ -859,7 +863,8 @@ class DisableEditingHeader(bpy.types.Operator):
bl_description = "Cancel unsaved header information"
def execute(self, context):
context.scene.BIMProjectProperties.is_editing = False
props = tool.Project.get_project_props()
props.is_editing = False
return {"FINISHED"}
@@ -975,16 +980,18 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
if not self.is_advanced and not self.should_start_fresh_session:
bpy.ops.bim.convert_to_blender()
context.scene.BIMProperties.ifc_file = filepath
bim_props = tool.Blender.get_bim_props()
bim_props.ifc_file = filepath
if not tool.Ifc.get():
self.report(
{"ERROR"},
f"Error loading IFC file from filepath '{filepath}'. See logs above in the system console for the details.",
)
return {"CANCELLED"}
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
context.scene.BIMProjectProperties.use_relative_project_path = self.use_relative_path
props = tool.Project.get_project_props()
props.is_loading = True
props.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
props.use_relative_project_path = self.use_relative_path
tool.Blender.register_toolbar()
tool.Project.add_recent_ifc_project(self.get_filepath_abs())
@@ -1034,13 +1041,15 @@ class RevertProject(bpy.types.Operator, IFCFileSelector):
@classmethod
def poll(cls, context):
if not context.scene.BIMProperties.ifc_file:
props = tool.Blender.get_bim_props()
if not props.ifc_file:
cls.poll_message_set("IFC project need to be loaded and saved on the disk.")
return False
return True
def execute(self, context):
bpy.ops.bim.load_project(should_start_fresh_session=True, filepath=context.scene.BIMProperties.ifc_file)
props = tool.Blender.get_bim_props()
bpy.ops.bim.load_project(should_start_fresh_session=True, filepath=props.ifc_file)
return {"FINISHED"}
@@ -1050,20 +1059,23 @@ class LoadProjectElements(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.props = context.scene.BIMProjectProperties
self.file = IfcStore.get_file()
self.props = tool.Project.get_project_props()
self.file = tool.Ifc.get()
bonsai.bim.schema.reload(self.file.schema_identifier)
start = time.time()
logger = logging.getLogger("ImportIFC")
path_log = tool.Blender.get_data_dir_path("process.log")
if not os.access(path_log.parent, os.W_OK):
path_log = os.path.join(tempfile.mkdtemp(), "process.log")
path_log = os.path.join(
tempfile.mkdtemp(dir=tool.Blender.get_addon_preferences().tmp_dir or None), "process.log"
)
logging.basicConfig(
filename=path_log,
filemode="a",
level=logging.DEBUG,
)
settings = import_ifc.IfcImportSettings.factory(context, context.scene.BIMProperties.ifc_file, logger)
props = tool.Blender.get_bim_props()
settings = import_ifc.IfcImportSettings.factory(context, props.ifc_file, logger)
settings.has_filter = self.props.filter_mode != "NONE"
settings.should_filter_spatial_elements = self.props.should_filter_spatial_elements
if self.props.filter_mode == "DECOMPOSITION":
@@ -1081,7 +1093,8 @@ class LoadProjectElements(bpy.types.Operator):
ifc_importer.execute()
settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
print("Import finished in {:.2f} seconds".format(time.time() - start))
context.scene.BIMProjectProperties.is_loading = False
props = tool.Project.get_project_props()
props.is_loading = False
tool.Project.load_pset_templates()
tool.Project.load_default_thumbnails()
@@ -1153,7 +1166,8 @@ class ToggleFilterCategories(bpy.types.Operator):
should_select: bpy.props.BoolProperty(name="Should Select", default=True)
def execute(self, context):
for filter_category in context.scene.BIMProjectProperties.filter_categories:
props = tool.Project.get_project_props()
for filter_category in props.filter_categories:
filter_category.is_selected = self.should_select
return {"FINISHED"}
@@ -1180,7 +1194,7 @@ class LinkIfc(bpy.types.Operator):
directory: str
def draw(self, context):
pprops = context.scene.BIMProjectProperties
pprops = tool.Project.get_project_props()
row = self.layout.row()
row.prop(self, "use_relative_path")
row = self.layout.row()
@@ -1201,7 +1215,8 @@ class LinkIfc(bpy.types.Operator):
if bpy.data.filepath and filepath.samefile(bpy.data.filepath):
self.report({"INFO"}, "Can't link the current .blend file")
continue
new = context.scene.BIMProjectProperties.links.add()
props = tool.Project.get_project_props()
new = props.links.add()
filepath = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path)
new.name = filepath
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
@@ -1232,9 +1247,10 @@ class UnlinkIfc(bpy.types.Operator):
def execute(self, context):
filepath = Path(self.filepath).as_posix()
bpy.ops.bim.unload_link(filepath=filepath)
index = context.scene.BIMProjectProperties.links.find(filepath)
props = tool.Project.get_project_props()
index = props.links.find(filepath)
if index != -1:
context.scene.BIMProjectProperties.links.remove(index)
props.links.remove(index)
return {"FINISHED"}
@@ -1254,8 +1270,9 @@ class UnloadLink(bpy.types.Operator):
if tool.Blender.ensure_blender_path_is_abs(Path(library.filepath)) == filepath:
bpy.data.libraries.remove(library)
links = context.scene.BIMProjectProperties.links
link = links.get(self.filepath)
props = tool.Project.get_project_props()
links = props.links
link = links[self.filepath]
# Let's assume that user might delete it.
if empty_handle := link.empty_handle:
bpy.data.objects.remove(empty_handle)
@@ -1264,7 +1281,7 @@ class UnloadLink(bpy.types.Operator):
if not any([l.is_loaded for l in links]):
ProjectDecorator.uninstall()
# we make sure we don't draw queried object from the file that was just unlinked
elif queried_obj := context.scene.BIMProjectProperties.queried_obj:
elif queried_obj := props.queried_obj:
queried_filepath = Path(queried_obj["ifc_filepath"])
if queried_filepath == filepath:
ProjectDecorator.uninstall()
@@ -1296,7 +1313,7 @@ class LoadLink(bpy.types.Operator):
def link_blend(self, filepath: Path) -> None:
with bpy.data.libraries.load(str(filepath), link=True) as (data_from, data_to):
data_to.scenes = data_from.scenes
link = bpy.context.scene.BIMProjectProperties.links[self.filepath]
link = tool.Project.get_project_props().links[self.filepath]
for scene in bpy.data.scenes:
if not scene.library or Path(scene.library.filepath) != filepath:
continue
@@ -1322,13 +1339,14 @@ class LoadLink(bpy.types.Operator):
if not blend_filepath.exists():
pprops = tool.Project.get_project_props()
gprops = bpy.context.scene.BIMGeoreferenceProperties
gprops = tool.Georeference.get_georeference_props()
code = f"""
import bpy
def run():
gprops = bpy.context.scene.BIMGeoreferenceProperties
import bonsai.tool as tool
gprops = tool.Georeference.get_georeference_props()
# Our model origin becomes their host model origin
gprops.host_model_origin = "{gprops.model_origin}"
gprops.host_model_origin_si = "{gprops.model_origin_si}"
@@ -1339,7 +1357,7 @@ def run():
gprops.blender_offset_z = "{gprops.blender_offset_z}"
gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}"
gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}"
pprops = bpy.context.scene.BIMProjectProperties
pprops = tool.Project.get_project_props()
pprops.distance_limit = {pprops.distance_limit}
pprops.false_origin_mode = "{pprops.false_origin_mode}"
pprops.false_origin = "{pprops.false_origin}"
@@ -1394,7 +1412,7 @@ except Exception as e:
with open(json_filepath, "r") as f:
data = json.load(f)
gprops = bpy.context.scene.BIMGeoreferenceProperties
gprops = tool.Georeference.get_georeference_props()
for prop in ("model_origin", "model_origin_si", "model_project_north"):
if (value := data.get(prop, None)) is not None:
setattr(gprops, prop, value)
@@ -1430,8 +1448,8 @@ class ToggleLinkSelectability(bpy.types.Operator):
link: bpy.props.StringProperty(name="Linked IFC Filepath")
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
props = tool.Project.get_project_props()
link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
link.is_selectable = (is_selectable := not link.is_selectable)
for collection in self.get_linked_collections():
@@ -1457,8 +1475,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
mode: bpy.props.EnumProperty(name="Visibility Mode", items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")))
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
props = tool.Project.get_project_props()
link = props.links[self.link]
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(Path(self.link).with_suffix(".ifc.cache.blend"))
if self.mode == "WIREFRAME":
self.toggle_wireframe(link)
@@ -1504,7 +1522,7 @@ class SelectLinkHandle(bpy.types.Operator):
index: bpy.props.IntProperty(name="Link Index")
def execute(self, context):
props = context.scene.BIMProjectProperties
props = tool.Project.get_project_props()
link = props.links[self.index]
handle = link.empty_handle
if not handle:
@@ -1546,8 +1564,9 @@ class ExportIFC(bpy.types.Operator):
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
return {"FINISHED"}
self.use_relative_path = context.scene.BIMProjectProperties.use_relative_project_path
if (filepath := context.scene.BIMProperties.ifc_file) and not self.should_save_as:
self.use_relative_path = tool.Project.get_project_props().use_relative_project_path
props = tool.Blender.get_bim_props()
if (filepath := props.ifc_file) and not self.should_save_as:
self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(filepath)))
return self.execute(context)
if not self.filepath:
@@ -1561,7 +1580,7 @@ class ExportIFC(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
project_props = context.scene.BIMProjectProperties
project_props = tool.Project.get_project_props()
project_props.use_relative_project_path = self.use_relative_path
if project_props.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size
@@ -1579,7 +1598,9 @@ class ExportIFC(bpy.types.Operator):
logger = logging.getLogger("ExportIFC")
path_log = tool.Blender.get_data_dir_path("process.log")
if not os.access(path_log.parent, os.W_OK):
path_log = os.path.join(tempfile.mkdtemp(), "process.log")
path_log = os.path.join(
tempfile.mkdtemp(dir=tool.Blender.get_addon_preferences().tmp_dir or None), "process.log"
)
logging.basicConfig(
filename=path_log,
filemode="a",
@@ -1606,18 +1627,20 @@ class ExportIFC(bpy.types.Operator):
print("Export finished in {:.2f} seconds".format(time.time() - start))
# New project created in Bonsai should be in recent projects too.
tool.Project.add_recent_ifc_project(Path(output_file))
scene = context.scene
if not scene.DocProperties.ifc_files:
new = scene.DocProperties.ifc_files.add()
props = tool.Drawing.get_document_props()
if not props.ifc_files:
new = props.ifc_files.add()
new.name = output_file
if context.scene.BIMProjectProperties.use_relative_project_path and bpy.data.is_saved:
props = tool.Project.get_project_props()
if props.use_relative_project_path and bpy.data.is_saved:
output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
if scene.BIMProperties.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
scene.BIMProperties.ifc_file = output_file
bim_props = tool.Blender.get_bim_props()
if bim_props.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
bim_props.ifc_file = output_file
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
if save_blend_file:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
bpy.context.scene.BIMProperties.is_dirty = False
bim_props.is_dirty = False
bonsai.bim.handler.refresh_ui_data()
self.report(
{"INFO"},
@@ -1655,8 +1678,8 @@ class LoadLinkedProject(bpy.types.Operator):
start = time.time()
pprops = bpy.context.scene.BIMProjectProperties
gprops = bpy.context.scene.BIMGeoreferenceProperties
pprops = tool.Project.get_project_props()
gprops = tool.Georeference.get_georeference_props()
self.filepath = Path(self.filepath).as_posix()
print("Processing", self.filepath)
@@ -1870,7 +1893,7 @@ class LoadLinkedProject(bpy.types.Operator):
mesh = bpy.data.meshes.new("Mesh")
geometry = shape.geometry
gprops = bpy.context.scene.BIMGeoreferenceProperties
gprops = tool.Georeference.get_georeference_props()
if (
gprops.has_blender_offset
and geometry.verts
@@ -1976,9 +1999,8 @@ class QueryLinkedElement(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
LinksData.linked_data = {}
props = context.scene.BIMProjectProperties
props = tool.Project.get_project_props()
props.queried_obj = None
props.quried_obj_root = None
for area in bpy.context.screen.areas:
if area.type == "PROPERTIES":
@@ -2114,6 +2136,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
def _execute(self, context):
from bonsai.bim.module.project.data import LinksData
props = tool.Project.get_project_props()
if not LinksData.linked_data:
self.report({"INFO"}, "No linked element found.")
return {"CANCELLED"}
@@ -2123,7 +2146,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
self.report({"INFO"}, "Cannot find Global Id for element.")
return {"CANCELLED"}
queried_obj = context.scene.BIMProjectProperties.queried_obj
queried_obj = props.queried_obj
ifc_file = tool.Ifc.get()
linked_ifc_file: ifcopenshell.file
@@ -2270,34 +2293,36 @@ class RefreshClippingPlanes(bpy.types.Operator):
def modal(self, context, event):
should_refresh = False
props = tool.Project.get_project_props()
self.clean_deleted_planes(context)
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
for clipping_plane in props.clipping_planes:
if clipping_plane.obj and self.is_moved(clipping_plane.obj):
should_refresh = True
break
total_planes = len(context.scene.BIMProjectProperties.clipping_planes)
total_planes = len(props.clipping_planes)
if should_refresh or total_planes != self.total_planes:
self.refresh_clipping_planes(context)
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
for clipping_plane in props.clipping_planes:
if clipping_plane.obj:
tool.Geometry.record_object_position(clipping_plane.obj)
self.total_planes = total_planes
return {"PASS_THROUGH"}
def clean_deleted_planes(self, context):
def clean_deleted_planes(self, context: bpy.types.Context) -> None:
props = tool.Project.get_project_props()
while True:
for i, clipping_plane in enumerate(context.scene.BIMProjectProperties.clipping_planes):
for i, clipping_plane in enumerate(props.clipping_planes):
if clipping_plane.obj:
try:
clipping_plane.obj.name
except:
context.scene.BIMProjectProperties.clipping_planes.remove(i)
props.clipping_planes.remove(i)
break
else:
context.scene.BIMProjectProperties.clipping_planes.remove(i)
props.clipping_planes.remove(i)
break
else:
break
@@ -2321,14 +2346,15 @@ class RefreshClippingPlanes(bpy.types.Operator):
region = next(r for r in area.regions if r.type == "WINDOW")
data = region.data
if not len(context.scene.BIMProjectProperties.clipping_planes):
props = tool.Project.get_project_props()
if not len(props.clipping_planes):
data.use_clip_planes = False
else:
with bpy.context.temp_override(area=area, region=region):
bpy.ops.view3d.clip_border()
clip_planes = []
for clipping_plane in bpy.context.scene.BIMProjectProperties.clipping_planes:
for clipping_plane in tool.Project.get_project_props().clipping_planes:
obj = clipping_plane.obj
if not obj:
continue
@@ -2367,8 +2393,8 @@ class CreateClippingPlane(bpy.types.Operator):
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
# Clean up deleted planes
if len(context.scene.BIMProjectProperties.clipping_planes) > 5:
props = tool.Project.get_project_props()
if len(props.clipping_planes) > 5:
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
return {"FINISHED"}
@@ -2408,7 +2434,7 @@ class CreateClippingPlane(bpy.types.Operator):
context.scene.cursor.location = location
new = context.scene.BIMProjectProperties.clipping_planes.add()
new = tool.Project.get_project_props().clipping_planes.add()
new.obj = plane_obj
tool.Blender.set_active_object(plane_obj)
@@ -2439,7 +2465,7 @@ class FlipClippingPlane(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
if obj in context.scene.BIMProjectProperties.clipping_planes_objs:
if obj in tool.Project.get_project_props().clipping_planes_objs:
obj.rotation_euler[0] += radians(180)
context.view_layer.update()
return {"FINISHED"}
@@ -2457,14 +2483,15 @@ class BIM_OT_save_clipping_planes(bpy.types.Operator):
@classmethod
def poll(cls, context):
if IfcStore.path:
return context.scene.BIMProjectProperties.clipping_planes
return tool.Project.get_project_props().clipping_planes
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
clipping_planes_to_serialize = defaultdict(dict)
clipping_planes = context.scene.BIMProjectProperties.clipping_planes
clipping_planes = tool.Project.get_project_props().clipping_planes
for clipping_plane in clipping_planes:
obj = clipping_plane.obj
assert obj
name = obj.name
clipping_planes_to_serialize[name]["location"] = obj.location[0:3]
clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3]
@@ -2490,13 +2517,14 @@ class BIM_OT_load_clipping_planes(bpy.types.Operator):
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
bpy.data.batch_remove(context.scene.BIMProjectProperties.clipping_planes_objs)
context.scene.BIMProjectProperties.clipping_planes.clear()
props = tool.Project.get_project_props()
bpy.data.batch_remove(props.clipping_planes_objs)
props.clipping_planes.clear()
with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file:
clipping_planes_dict = json.load(file)
for name, values in clipping_planes_dict.items():
bpy.ops.bim.create_clipping_plane()
obj = context.scene.BIMProjectProperties.clipping_planes_objs[-1]
obj = props.clipping_planes_objs[-1]
obj.name = name
obj.location = values["location"]
obj.rotation_euler = values["rotation"]
+16 -3
View File
@@ -121,7 +121,7 @@ def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context)
self.filter_categories.clear()
if self.filter_mode == "NONE":
return
file = IfcStore.get_file()
file = tool.Ifc.get()
if self.filter_mode == "DECOMPOSITION":
if file.schema == "IFC2X3":
elements = file.by_type("IfcSpatialStructureElement")
@@ -180,6 +180,8 @@ class LibraryElement(PropertyGroup):
element_type: EnumProperty(items=[(i, i, "") for i in get_args(LibraryElementType)], name="Element Type")
# Asset group.
asset_count: IntProperty(name="Asset Count")
# Asset library.
has_sublibraries: BoolProperty(name="Has Sublibraries", default=False)
# Asset.
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_declared: BoolProperty(name="Is Declared", default=False)
@@ -194,6 +196,7 @@ class LibraryElement(PropertyGroup):
name: str
element_type: LibraryElementType
asset_count: int
has_sublibraries: bool
ifc_definition_id: int
is_declared: bool
is_appended: bool
@@ -299,7 +302,14 @@ class BIMProjectProperties(PropertyGroup):
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
should_clean_mesh: BoolProperty(name="Clean Meshes", default=False)
should_clean_mesh: BoolProperty(
name="Clean Meshes",
description=(
"Convert all triangles to quads for meshes. "
"By default Bonsai is importing meshes triangulated (even if they are not stored as triangulated in IFC)."
),
default=False,
)
should_cache: BoolProperty(name="Cache", default=False)
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
@@ -395,12 +405,15 @@ class BIMProjectProperties(PropertyGroup):
def clipping_planes_objs(self) -> list[bpy.types.Object]:
return list({cp.obj for cp in self.clipping_planes if cp.obj})
def add_library_project_library(self, name: str, asset_count: int, ifc_definition_id: int) -> LibraryElement:
def add_library_project_library(
self, name: str, asset_count: int, ifc_definition_id: int, has_sublibraries: bool
) -> LibraryElement:
new = self.library_elements.add()
new["name"] = name
new.asset_count = asset_count
new.element_type = "LIBRARY"
new.ifc_definition_id = ifc_definition_id
new.has_sublibraries = has_sublibraries
return new
def add_library_asset_class(self, name: str, asset_count: int) -> LibraryElement:
+32 -13
View File
@@ -28,7 +28,7 @@ from bonsai.bim.module.project.data import ProjectData, LinksData
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.project.prop import LibraryElement, BIMProjectProperties
from bonsai.bim.module.project.prop import LibraryElement, BIMProjectProperties, FilterCategory, Link
def file_import_menu(self, context):
@@ -151,9 +151,9 @@ class BIM_PT_project(Panel):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
props = context.scene.BIMProperties
props = tool.Blender.get_bim_props()
pprops = self.props = tool.Project.get_project_props()
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
if pprops.is_loading:
self.draw_advanced_loading_ui(context)
elif self.file or props.ifc_file:
@@ -247,7 +247,7 @@ class BIM_PT_project(Panel):
def draw_editing_buttons(self, context, row):
pprops = self.props
if IfcStore.get_file():
if tool.Ifc.get():
if pprops.is_editing:
row.operator("bim.edit_header", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_header", icon="CANCEL", text="")
@@ -257,10 +257,10 @@ class BIM_PT_project(Panel):
def draw_editable_file_info(self, context):
pprops = self.props
if IfcStore.get_file():
if tool.Ifc.get():
row = self.layout.row(align=True)
row.label(text="IFC Schema", icon="FILE_CACHE")
row.label(text=IfcStore.get_file().schema)
row.label(text=tool.Ifc.get().schema)
if pprops.is_editing:
row = self.layout.row(align=True)
@@ -281,7 +281,7 @@ class BIM_PT_project(Panel):
else:
row = self.layout.row(align=True)
row.label(text="IFC MVD", icon="FILE_HIDDEN")
mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description)
mvd = "".join(tool.Ifc.get().wrapped_data.header.file_description.description)
if "[" in mvd:
mvd = mvd.split("[")[1][0:-1]
row.label(text=mvd)
@@ -305,7 +305,7 @@ class BIM_PT_project(Panel):
def draw_loaded_project_ui(self, context):
# file name row
props = context.scene.BIMProperties
props = tool.Blender.get_bim_props()
file_name_row = self.layout.row(align=True)
file_name_row.label(text=os.path.basename(props.ifc_file), icon="FILE")
self.draw_editing_buttons(context, file_name_row)
@@ -315,7 +315,7 @@ class BIM_PT_project(Panel):
# file path row and actions section
row = self.layout.row(align=True)
if context.scene.BIMProperties.is_dirty:
if props.is_dirty:
row.label(text="Saved*", icon="EXPORT")
else:
row.label(text="Saved", icon="EXPORT")
@@ -339,7 +339,7 @@ class BIM_PT_new_project_wizard(Panel):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
props = context.scene.BIMProperties
props = tool.Blender.get_bim_props()
pprops = tool.Project.get_project_props()
prop_with_search(self.layout, pprops, "export_schema")
row = self.layout.row()
@@ -512,7 +512,7 @@ class BIM_UL_library(UIList):
):
if item:
row = layout.row(align=True)
if item.element_type != "ASSET" and item.asset_count > 0:
if item.element_type != "ASSET" and (item.asset_count > 0 or item.has_sublibraries):
op = row.operator("bim.change_library_element", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.element_name = item.name
op.breadcrumb_type = item.element_type
@@ -542,7 +542,16 @@ class BIM_UL_library(UIList):
class BIM_UL_filter_categories(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMProjectProperties,
item: FilterCategory,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.total_elements})")
@@ -556,7 +565,17 @@ class BIM_UL_filter_categories(UIList):
class BIM_UL_links(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMProjectProperties,
item: Link,
icon,
active_data,
active_propname,
index,
):
if item:
row = layout.row(align=True)
if item.is_loaded:
@@ -94,7 +94,7 @@ class EditPset(bpy.types.Operator, tool.Ifc.Operator):
properties: bpy.props.StringProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
@@ -226,7 +226,7 @@ class AddQto(bpy.types.Operator, tool.Ifc.Operator):
obj_type: bpy.props.StringProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
qto_name = tool.Pset.get_pset_name(self.obj, self.obj_type, pset_type="QTO")
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
@@ -314,7 +314,7 @@ class BIM_OT_rename_parameters(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props_to_map = context.scene.RenameProperties
ifc_file = IfcStore.get_file()
ifc_file = tool.Ifc.get()
all_ifc_elements = ifc_file.by_type("IfcElement")
for ifc_element in all_ifc_elements:
@@ -347,7 +347,7 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator, tool.Ifc.Operator):
index: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = context.scene.AddEditProperties
for obj in tool.Blender.get_selected_objects():
@@ -402,7 +402,7 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator, tool.Ifc.Operator):
index: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
props = context.scene.DeletePsets
for obj in tool.Blender.get_selected_objects():
+5 -5
View File
@@ -26,7 +26,6 @@ import bonsai.tool as tool
from bonsai.bim.prop import Attribute, StrProperty
from bonsai.bim.module.pset.data import AddEditCustomPropertiesData, ObjectPsetsData, MaterialPsetsData
from bonsai.bim.module.material.data import ObjectMaterialData
from bonsai.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -135,7 +134,7 @@ def get_resource_pset_names(self, context):
global psetnames
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
if ifc_class not in psetnames:
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
@@ -146,7 +145,7 @@ def get_resource_qto_names(self, context):
global qtonames
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
if ifc_class not in qtonames:
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
@@ -174,7 +173,7 @@ def get_group_qto_names(self, context):
def get_profile_pset_names(self, context):
global psetnames
pprops = tool.Profile.get_profile_props()
ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a()
ifc_class = tool.Ifc.get().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a()
if ifc_class not in psetnames:
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
@@ -290,7 +289,8 @@ class AddEditProperties(PropertyGroup):
enum_values: CollectionProperty(name="Enum Values", type=Attribute)
def get_value_name(self) -> Union[Literal["string_value", "bool_value", "int_value", "float_value"], None]:
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
schema = tool.Ifc.schema()
ifc_data_type = schema.declaration_by_name(self.primary_measure_type)
data_type = ifcopenshell.util.attribute.get_primitive_type(ifc_data_type)
if data_type == "string":
return "string_value"
+2 -3
View File
@@ -20,7 +20,6 @@ from __future__ import annotations
import bpy
import bonsai.tool as tool
from bpy.types import Panel
from bonsai.bim.ifc import IfcStore
from bonsai.bim.helper import prop_with_search, get_display_value
from bonsai.bim.module.pset.data import (
ObjectPsetsData,
@@ -247,7 +246,7 @@ class BIM_PT_object_psets(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
return True
@@ -325,7 +324,7 @@ class BIM_PT_object_qtos(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
return False
return True
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell
import ifcopenshell.util.attribute
from ifcopenshell.util.doc import get_attribute_doc
import bonsai.tool as tool
from bonsai.bim.module.pset_template.data import PsetTemplatesData
from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.ifc import IfcStore
@@ -183,7 +184,8 @@ class PropTemplate(PropertyGroup):
def get_value_name(self) -> str:
if self.primary_measure_type == "-":
return "string_value"
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
schema = tool.Ifc.schema()
ifc_data_type = schema.declaration_by_name(self.primary_measure_type)
data_type = ifcopenshell.util.attribute.get_primitive_type(ifc_data_type)
if data_type == "string":
return "string_value"
@@ -451,7 +451,8 @@ def get_side_area(o: bpy.types.Object) -> float:
def get_cross_section_area(obj: bpy.types.Object) -> float:
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
representation = tool.Geometry.get_active_representation(obj)
assert representation
item = representation.Items[0]
while True:
if item.is_a("IfcExtrudedAreaSolid"):
@@ -21,7 +21,6 @@ import ifcopenshell
import ifcopenshell.api
import bonsai.tool as tool
import bonsai.core.qto as core
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.qto import helper
@@ -19,7 +19,6 @@
import bpy
import ifcopenshell.api
import ifcopenshell.util.resource
from bonsai.bim.ifc import IfcStore
import bonsai.tool as tool
import bonsai.bim.module.pset.data
import bonsai.bim.module.resource.data
+2 -2
View File
@@ -17,9 +17,9 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
import bonsai.bim.helper
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.resource.data import ResourceData
from typing import Any
@@ -35,7 +35,7 @@ class BIM_PT_resources(Panel):
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
file = tool.Ifc.get()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
+7 -23
View File
@@ -54,32 +54,14 @@ class IfcClassData:
@classmethod
def ifc_products(cls):
products = [
"IfcElementType",
"IfcElement",
"IfcFeatureElement",
"IfcSpatialElement",
"IfcSpatialElementType",
"IfcStructuralItem",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
products = tool.Root.get_ifc_products()
version = tool.Ifc.get_schema()
if version == "IFC2X3":
products = [
"IfcElementType",
"IfcElement",
"IfcFeatureElement",
"IfcSpatialStructureElement",
"IfcStructuralItem",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
return [(e, e, (get_entity_doc(version, e) or {}).get("description", "")) for e in products]
@classmethod
def ifc_classes(cls):
ifc_product = bpy.context.scene.BIMRootProperties.ifc_product
rprops = tool.Root.get_root_props()
ifc_product = rprops.ifc_product
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
@@ -99,7 +81,8 @@ class IfcClassData:
@classmethod
def ifc_predefined_types(cls):
types_enum = []
ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
rprops = tool.Root.get_root_props()
ifc_class = rprops.ifc_class
declaration = tool.Ifc.schema().declaration_by_name(ifc_class)
version = tool.Ifc.get_schema()
for attribute in declaration.attributes():
@@ -133,7 +116,8 @@ class IfcClassData:
@classmethod
def representation_template(cls):
ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
rprops = tool.Root.get_root_props()
ifc_class = rprops.ifc_class
templates = [
("EMPTY", "No Geometry", "Start with an empty object"),
None,
+28 -27
View File
@@ -42,32 +42,33 @@ class EnableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
rprops = tool.Root.get_root_props()
obj = context.active_object
self.file = tool.Ifc.get()
ifc_class = obj.name.split("/")[0]
element = tool.Ifc.get_entity(obj)
assert element
ifc_class = element.is_a()
context.active_object.BIMObjectProperties.is_reassigning_class = True
ifc_products = [
"IfcElement",
"IfcElementType",
"IfcSpatialElement",
"IfcGroup",
"IfcStructural",
"IfcPositioningElement",
"IfcContext",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
ifc_products = tool.Root.get_ifc_products()
schema = tool.Ifc.schema()
declaration = schema.declaration_by_name(ifc_class)
for ifc_product in ifc_products:
if ifcopenshell.util.schema.is_a(IfcStore.get_schema().declaration_by_name(ifc_class), ifc_product):
context.scene.BIMRootProperties.ifc_product = ifc_product
if ifcopenshell.util.schema.is_a(declaration, ifc_product):
rprops.ifc_product = ifc_product
break
else:
self.report({"ERROR"}, f"Couldn't find matching IFC product for the selected object: '{element}'.")
obj.BIMObjectProperties.is_reassigning_class = False
return {"CANCELLED"}
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context.scene.BIMRootProperties.ifc_class = element.is_a()
context.scene.BIMRootProperties.relating_class_object = None
rprops.ifc_class = element.is_a()
rprops.relating_class_object = None
if hasattr(element, "PredefinedType"):
if element.PredefinedType:
context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
rprops.ifc_predefined_type = element.PredefinedType
userdefined_type = ifcopenshell.util.element.get_predefined_type(element)
context.scene.BIMRootProperties.ifc_userdefined_type = userdefined_type or ""
rprops.ifc_userdefined_type = userdefined_type or ""
return {"FINISHED"}
@@ -94,9 +95,9 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
else:
objects = set(context.selected_objects + [context.active_object])
self.file = tool.Ifc.get()
root_props = context.scene.BIMRootProperties
ifc_product: str = root_props.ifc_product
ifc_class: str = root_props.ifc_class
root_props = tool.Root.get_root_props()
ifc_product = root_props.ifc_product
ifc_class = root_props.ifc_class
type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema)), None)
predefined_type = root_props.ifc_predefined_type
@@ -176,7 +177,7 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
ifc_representation_class: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
objects: list[bpy.types.Object] = []
if self.obj:
objects = [bpy.data.objects[self.obj]]
@@ -358,7 +359,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
return IfcStore.execute_ifc_operator(self, context, event, method="INVOKE")
def _invoke(self, context, event):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
# For convenience, preselect OBJs if applicable
if props.ifc_product == "IfcFeatureElement":
if (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH":
@@ -376,7 +377,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
def _execute(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
predefined_type = (
props.ifc_userdefined_type if props.ifc_predefined_type == "USERDEFINED" else props.ifc_predefined_type
)
@@ -500,7 +501,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
else:
material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown")
if representation_template == "PROFILESET":
profile_id = tool.Blender.get_enum_safe(context.scene.BIMRootProperties, "profile")
profile_id = tool.Blender.get_enum_safe(props, "profile")
if profile_id in ("-", None):
profile = next((p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName), None)
if profile is None:
@@ -589,7 +590,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.set_active_object(obj)
def draw(self, context):
props = context.scene.BIMRootProperties
props = tool.Root.get_root_props()
self.layout.use_property_split = True
self.layout.use_property_decorate = False
row = self.layout.row()
@@ -600,7 +601,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
if not self.ifc_product:
prop_with_search(self.layout, props, "ifc_product", text="Definition", should_click_ok=True)
prop_with_search(self.layout, props, "ifc_class", should_click_ok=True)
ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
ifc_predefined_types = root_prop.get_ifc_predefined_types(props, context)
if ifc_predefined_types:
prop_with_search(self.layout, props, "ifc_predefined_type", should_click_ok=True)
if props.ifc_predefined_type == "USERDEFINED":

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