This commit is contained in:
Andrej730
2024-08-01 10:36:03 +05:00
parent db5167bbaf
commit c8466e0606
13 changed files with 254 additions and 159 deletions
@@ -32,7 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "FLOORING" return relating_type == "FLOORING"
@@ -51,7 +51,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "CEILING" return relating_type == "CEILING"
@@ -91,7 +91,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object) element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2": if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "FLOORING" return context.selected_objects and relating_type == "FLOORING"
@@ -119,7 +119,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object) element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id) relating_type_id = int(bpy.context.scene.BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id)) relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2": if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "CEILING" return context.selected_objects and relating_type == "CEILING"
+18 -8
View File
@@ -16,8 +16,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
def add_instance_flooring_covering_from_cursor(ifc, root, spatial): if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def add_instance_flooring_covering_from_cursor(ifc: tool.Ifc, root: tool.Root, spatial: tool.Spatial) -> None:
if not root.get_default_container(): if not root.get_default_container():
raise NoDefaultContainer() raise NoDefaultContainer()
@@ -32,7 +40,7 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial):
relating_type = None relating_type = None
if selected_objects and active_obj: if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
else: else:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
@@ -59,7 +67,9 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial):
spatial.regen_obj_representation(obj, body) spatial.regen_obj_representation(obj, body)
def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial): def add_instance_ceiling_covering_from_cursor(
ifc: tool.Ifc, root: tool.Root, covering: tool.Covering, spatial: tool.Spatial
) -> None:
if not root.get_default_container(): if not root.get_default_container():
raise NoDefaultContainer() raise NoDefaultContainer()
@@ -74,7 +84,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial):
relating_type = None relating_type = None
if selected_objects and active_obj: if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
else: else:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
ceiling_height = covering.get_z_from_ceiling_height() ceiling_height = covering.get_z_from_ceiling_height()
@@ -101,7 +111,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial):
spatial.regen_obj_representation(obj, body) spatial.regen_obj_representation(obj, body)
def regen_selected_covering_object(root, spatial): def regen_selected_covering_object(root: tool.Root, spatial: tool.Spatial) -> None:
if not root.get_default_container(): if not root.get_default_container():
raise NoDefaultContainer() raise NoDefaultContainer()
@@ -109,7 +119,7 @@ def regen_selected_covering_object(root, spatial):
selected_objects = spatial.get_selected_objects() selected_objects = spatial.get_selected_objects()
if selected_objects and active_obj: if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y) space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
@@ -132,7 +142,7 @@ def regen_selected_covering_object(root, spatial):
# TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS # TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS
def add_instance_flooring_coverings_from_walls(root, spatial): def add_instance_flooring_coverings_from_walls(root: tool.Root, spatial: tool.Spatial) -> None:
if not root.get_default_container(): if not root.get_default_container():
raise NoDefaultContainer() raise NoDefaultContainer()
@@ -158,7 +168,7 @@ def add_instance_flooring_coverings_from_walls(root, spatial):
spatial.regen_obj_representation(obj, body) spatial.regen_obj_representation(obj, body)
def add_instance_ceiling_coverings_from_walls(root, spatial, covering): def add_instance_ceiling_coverings_from_walls(root: tool.Root, spatial: tool.Spatial, covering: tool.Covering) -> None:
if not root.get_default_container(): if not root.get_default_container():
raise NoDefaultContainer() raise NoDefaultContainer()
+7 -2
View File
@@ -18,11 +18,12 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING: if TYPE_CHECKING:
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.representation
import blenderbim.tool as tool import blenderbim.tool as tool
@@ -225,7 +226,11 @@ def disable_editing_drawings(drawing: tool.Drawing) -> None:
def add_drawing( def add_drawing(
ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None ifc: tool.Ifc,
collector: tool.Collector,
drawing: tool.Drawing,
target_view: Union[ifcopenshell.util.representation.TARGET_VIEW, None] = None,
location_hint: Union[str, None] = None,
) -> None: ) -> None:
drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint)) drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint))
drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint) drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
+1 -1
View File
@@ -173,7 +173,7 @@ def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: t
relating_type = None relating_type = None
if selected_objects and active_obj: if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
element = ifc.get_entity(active_obj) element = ifc.get_entity(active_obj)
else: else:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
+1 -1
View File
@@ -888,7 +888,7 @@ class Spatial:
def get_boundary_lines_from_context_visible_objects(cls): pass def get_boundary_lines_from_context_visible_objects(cls): pass
def get_gross_mesh_from_element(cls, visible_element): pass def get_gross_mesh_from_element(cls, visible_element): pass
def create_mesh_from_shape(cls, shape): pass def create_mesh_from_shape(cls, shape): pass
def get_x_y_z_h_mat_from_active_obj(cls, active_obj): pass def get_x_y_z_h_mat_from_obj(cls, obj): pass
def get_x_y_z_h_mat_from_cursor(cls): pass def get_x_y_z_h_mat_from_cursor(cls): pass
def get_union_shape_from_selected_objects(cls): pass def get_union_shape_from_selected_objects(cls): pass
def get_boundary_elements(cls, selected_objects): pass def get_boundary_elements(cls, selected_objects): pass
+1 -1
View File
@@ -33,7 +33,7 @@ from shapely import Polygon, MultiPolygon
class Covering(blenderbim.core.tool.Covering): class Covering(blenderbim.core.tool.Covering):
@classmethod @classmethod
def get_z_from_ceiling_height(cls): def get_z_from_ceiling_height(cls) -> float:
props = bpy.context.scene.BIMCoveringProperties props = bpy.context.scene.BIMCoveringProperties
return props.ceiling_height return props.ceiling_height
+127 -92
View File
@@ -19,6 +19,7 @@
import os import os
import re import re
import collections import collections
import collections.abc
import bpy import bpy
import math import math
import json import json
@@ -79,11 +80,11 @@ class Drawing(blenderbim.core.tool.Drawing):
# fmt: on # fmt: on
@classmethod @classmethod
def canonicalise_class_name(cls, name): def canonicalise_class_name(cls, name: str) -> str:
return re.sub("[^0-9a-zA-Z]+", "", name) return re.sub("[^0-9a-zA-Z]+", "", name)
@classmethod @classmethod
def copy_representation(cls, source, dest): def copy_representation(cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance) -> None:
if source.Representation: if source.Representation:
dest.Representation = ifcopenshell.util.element.copy_deep( dest.Representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"]
@@ -114,7 +115,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return obj return obj
@classmethod @classmethod
def ensure_annotation_in_drawing_plane(cls, obj, camera=None): def ensure_annotation_in_drawing_plane(
cls, obj: bpy.types.Object, camera: Optional[bpy.types.Object] = None
) -> None:
"""Make sure annotation object is going to be visible in the camera view""" """Make sure annotation object is going to be visible in the camera view"""
def get_camera_from_annotation_object(obj): def get_camera_from_annotation_object(obj):
@@ -138,7 +141,9 @@ class Drawing(blenderbim.core.tool.Drawing):
ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA") ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA")
@classmethod @classmethod
def setup_annotation_object(cls, obj, object_type, related_object=None): def setup_annotation_object(
cls, obj: bpy.types.Object, object_type: str, related_object: Optional[bpy.types.Object] = None
) -> None:
"""Finish object's adjustments after both object and entity are created""" """Finish object's adjustments after both object and entity are created"""
if not related_object: if not related_object:
@@ -205,7 +210,9 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Drawing.update_text_value(obj) tool.Drawing.update_text_value(obj)
@classmethod @classmethod
def is_annotation_object_type(cls, element, object_types): def is_annotation_object_type(
cls, element: ifcopenshell.entity_instance, object_types: Union[str, list[str]]
) -> bool:
if not isinstance(object_types, collections.abc.Iterable): if not isinstance(object_types, collections.abc.Iterable):
object_types = [object_types] object_types = [object_types]
@@ -226,7 +233,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return False return False
@classmethod @classmethod
def get_annotation_representation(cls, element): def get_annotation_representation(
cls, element: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
rep = ifcopenshell.util.representation.get_representation( rep = ifcopenshell.util.representation.get_representation(
element, "Plan", "Annotation" element, "Plan", "Annotation"
) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation") ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
@@ -237,7 +246,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return rep return rep
@classmethod @classmethod
def create_camera(cls, name, matrix, location_hint): def create_camera(
cls, name: str, matrix: Matrix, location_hint: Literal["PERPSECTIVE", "ORTHOGRAPHIC"]
) -> bpy.types.Object:
camera = bpy.data.objects.new(name, bpy.data.cameras.new(name)) camera = bpy.data.objects.new(name, bpy.data.cameras.new(name))
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
camera.data.show_limits = True camera.data.show_limits = True
@@ -257,7 +268,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return camera return camera
@classmethod @classmethod
def create_svg_schedule(cls, schedule): def create_svg_schedule(cls, schedule: ifcopenshell.entity_instance) -> None:
import blenderbim.bim.module.drawing.scheduler as scheduler import blenderbim.bim.module.drawing.scheduler as scheduler
schedule_creator = scheduler.Scheduler() schedule_creator = scheduler.Scheduler()
@@ -276,7 +287,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return uri return uri
@classmethod @classmethod
def add_drawings(cls, sheet): def add_drawings(cls, sheet: ifcopenshell.entity_instance) -> None:
import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.sheeter as sheeter
sheet_builder = sheeter.SheetBuilder() sheet_builder = sheeter.SheetBuilder()
@@ -293,7 +304,7 @@ class Drawing(blenderbim.core.tool.Drawing):
sheet_builder.add_drawing(drawing_references[drawing_annotation.Name], drawing_annotation, sheet) sheet_builder.add_drawing(drawing_references[drawing_annotation.Name], drawing_annotation, sheet)
@classmethod @classmethod
def delete_collection(cls, collection): def delete_collection(cls, collection: bpy.types.Collection) -> None:
bpy.data.collections.remove(collection, do_unlink=True) bpy.data.collections.remove(collection, do_unlink=True)
@classmethod @classmethod
@@ -312,19 +323,19 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
@classmethod @classmethod
def disable_editing_drawings(cls): def disable_editing_drawings(cls) -> None:
bpy.context.scene.DocProperties.is_editing_drawings = False bpy.context.scene.DocProperties.is_editing_drawings = False
@classmethod @classmethod
def disable_editing_schedules(cls): def disable_editing_schedules(cls) -> None:
bpy.context.scene.DocProperties.is_editing_schedules = False bpy.context.scene.DocProperties.is_editing_schedules = False
@classmethod @classmethod
def disable_editing_references(cls): def disable_editing_references(cls) -> None:
bpy.context.scene.DocProperties.is_editing_references = False bpy.context.scene.DocProperties.is_editing_references = False
@classmethod @classmethod
def disable_editing_sheets(cls): def disable_editing_sheets(cls) -> None:
bpy.context.scene.DocProperties.is_editing_sheets = False bpy.context.scene.DocProperties.is_editing_sheets = False
@classmethod @classmethod
@@ -344,19 +355,19 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
@classmethod @classmethod
def enable_editing_drawings(cls): def enable_editing_drawings(cls) -> None:
bpy.context.scene.DocProperties.is_editing_drawings = True bpy.context.scene.DocProperties.is_editing_drawings = True
@classmethod @classmethod
def enable_editing_schedules(cls): def enable_editing_schedules(cls) -> None:
bpy.context.scene.DocProperties.is_editing_schedules = True bpy.context.scene.DocProperties.is_editing_schedules = True
@classmethod @classmethod
def enable_editing_references(cls): def enable_editing_references(cls) -> None:
bpy.context.scene.DocProperties.is_editing_references = True bpy.context.scene.DocProperties.is_editing_references = True
@classmethod @classmethod
def enable_editing_sheets(cls): def enable_editing_sheets(cls) -> None:
bpy.context.scene.DocProperties.is_editing_sheets = True bpy.context.scene.DocProperties.is_editing_sheets = True
@classmethod @classmethod
@@ -368,14 +379,14 @@ class Drawing(blenderbim.core.tool.Drawing):
obj.BIMAssignedProductProperties.is_editing_product = True obj.BIMAssignedProductProperties.is_editing_product = True
@classmethod @classmethod
def ensure_unique_drawing_name(cls, name): def ensure_unique_drawing_name(cls, name: str) -> str:
names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
while name in names: while name in names:
name += "-X" name += "-X"
return name return name
@classmethod @classmethod
def ensure_unique_identification(cls, identification): def ensure_unique_identification(cls, identification: str) -> str:
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
ids = [d.DocumentId for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"] ids = [d.DocumentId for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]
else: else:
@@ -431,7 +442,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", target_view) return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", target_view)
@classmethod @classmethod
def get_body_context(cls): def get_body_context(cls) -> ifcopenshell.entity_instance:
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@classmethod @classmethod
@@ -462,15 +473,15 @@ class Drawing(blenderbim.core.tool.Drawing):
return os.path.splitext(os.path.basename(path))[0] return os.path.splitext(os.path.basename(path))[0]
@classmethod @classmethod
def get_path_with_ext(cls, path, ext): def get_path_with_ext(cls, path: str, ext: str) -> str:
return os.path.splitext(path)[0] + f".{ext}" return os.path.splitext(path)[0] + f".{ext}"
@classmethod @classmethod
def get_unit_system(cls): def get_unit_system(cls) -> Literal["NONE", "METRIC", "IMPERIAL"]:
return bpy.context.scene.unit_settings.system return bpy.context.scene.unit_settings.system
@classmethod @classmethod
def get_drawing_collection(cls, drawing): def get_drawing_collection(cls, drawing: ifcopenshell.entity_instance) -> Union[bpy.types.Collection, None]:
obj = tool.Ifc.get_object(drawing) obj = tool.Ifc.get_object(drawing)
if obj: if obj:
return obj.BIMObjectProperties.collection return obj.BIMObjectProperties.collection
@@ -488,8 +499,8 @@ class Drawing(blenderbim.core.tool.Drawing):
return rel.RelatingDocument return rel.RelatingDocument
@classmethod @classmethod
def get_drawing_references(cls, drawing): def get_drawing_references(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
results = set() results: set[ifcopenshell.entity_instance] = set()
for inverse in tool.Ifc.get().get_inverse(drawing): for inverse in tool.Ifc.get().get_inverse(drawing):
if inverse.is_a("IfcRelAssignsToProduct") and inverse.RelatingProduct == drawing: if inverse.is_a("IfcRelAssignsToProduct") and inverse.RelatingProduct == drawing:
results.update(inverse.RelatedObjects) results.update(inverse.RelatedObjects)
@@ -505,7 +516,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return rel.RelatedObjects return rel.RelatedObjects
@classmethod @classmethod
def get_ifc_representation_class(cls, object_type): def get_ifc_representation_class(cls, object_type: str) -> str:
if object_type == "TEXT": if object_type == "TEXT":
return "IfcTextLiteral" return "IfcTextLiteral"
elif object_type == "TEXT_LEADER": elif object_type == "TEXT_LEADER":
@@ -517,7 +528,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return element.Name return element.Name
@classmethod @classmethod
def generate_drawing_matrix(cls, target_view, location_hint): def generate_drawing_matrix(
cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str
) -> Matrix:
x, y, z = (0, 0, 0) if location_hint == 0 else bpy.context.scene.cursor.matrix.translation x, y, z = (0, 0, 0) if location_hint == 0 else bpy.context.scene.cursor.matrix.translation
if target_view == "PLAN_VIEW": if target_view == "PLAN_VIEW":
if location_hint: if location_hint:
@@ -554,12 +567,14 @@ class Drawing(blenderbim.core.tool.Drawing):
return mathutils.Matrix() return mathutils.Matrix()
@classmethod @classmethod
def generate_sheet_identification(cls): def generate_sheet_identification(cls) -> str:
number = len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]) number = len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
return "A" + str(number).zfill(2) return "A" + str(number).zfill(2)
@classmethod @classmethod
def get_text_literal(cls, obj, return_list=False): def get_text_literal(
cls, obj: bpy.types.Object, return_list: bool = False
) -> Union[ifcopenshell.entity_instance, None, list[ifcopenshell.entity_instance]]:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
return return
@@ -575,11 +590,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return items[0] return items[0]
@classmethod @classmethod
def is_editing_sheets(cls): def is_editing_sheets(cls) -> bool:
return bpy.context.scene.DocProperties.is_editing_sheets return bpy.context.scene.DocProperties.is_editing_sheets
@classmethod @classmethod
def remove_literal_from_annotation(cls, obj, literal): def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
return return
@@ -617,7 +632,9 @@ class Drawing(blenderbim.core.tool.Drawing):
cls.remove_literal_from_annotation(obj, literal) cls.remove_literal_from_annotation(obj, literal)
@classmethod @classmethod
def add_literal_to_annotation(cls, obj, Literal="Literal", Path="RIGHT", BoxAlignment="bottom-left"): def add_literal_to_annotation(
cls, obj: bpy.types.Object, Literal: str = "Literal", Path: str = "RIGHT", BoxAlignment: str = "bottom-left"
) -> Union[ifcopenshell.entity_instance, None]:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
return return
@@ -831,7 +848,7 @@ class Drawing(blenderbim.core.tool.Drawing):
new.identification = schedule.Identification new.identification = schedule.Identification
@classmethod @classmethod
def import_sheets(cls): def import_sheets(cls) -> None:
props = bpy.context.scene.DocProperties props = bpy.context.scene.DocProperties
expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded} expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded}
props.sheets.clear() props.sheets.clear()
@@ -868,7 +885,7 @@ class Drawing(blenderbim.core.tool.Drawing):
new.reference_type = reference_description new.reference_type = reference_description
@classmethod @classmethod
def get_active_sheet(cls, context): def get_active_sheet(cls, context: bpy.types.Context) -> bpy.types.PropertyGroup:
props = context.scene.DocProperties props = context.scene.DocProperties
return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet) return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet)
@@ -904,7 +921,7 @@ class Drawing(blenderbim.core.tool.Drawing):
obj.BIMAssignedProductProperties.relating_product = None obj.BIMAssignedProductProperties.relating_product = None
@classmethod @classmethod
def open_with_user_command(cls, user_command, path): def open_with_user_command(cls, user_command: str, path: str) -> None:
if user_command: if user_command:
commands = json.loads(user_command) commands = json.loads(user_command)
replacements = {"path": path} replacements = {"path": path}
@@ -920,15 +937,15 @@ class Drawing(blenderbim.core.tool.Drawing):
subprocess.call(("xdg-open", path)) subprocess.call(("xdg-open", path))
@classmethod @classmethod
def open_spreadsheet(cls, uri): def open_spreadsheet(cls, uri: str) -> None:
cls.open_with_user_command(tool.Blender.get_addon_preferences().spreadsheet_command, uri) cls.open_with_user_command(tool.Blender.get_addon_preferences().spreadsheet_command, uri)
@classmethod @classmethod
def open_svg(cls, uri): def open_svg(cls, uri: str) -> None:
cls.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, uri) cls.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, uri)
@classmethod @classmethod
def open_layout_svg(cls, uri): def open_layout_svg(cls, uri: str) -> None:
cls.open_with_user_command(tool.Blender.get_addon_preferences().layout_svg_command, uri) cls.open_with_user_command(tool.Blender.get_addon_preferences().layout_svg_command, uri)
@classmethod @classmethod
@@ -954,15 +971,17 @@ class Drawing(blenderbim.core.tool.Drawing):
) )
@classmethod @classmethod
def set_drawing_collection_name(cls, drawing, collection): def set_drawing_collection_name(
cls, drawing: ifcopenshell.entity_instance, collection: bpy.types.Collection
) -> None:
collection.name = tool.Loader.get_name(drawing) collection.name = tool.Loader.get_name(drawing)
@classmethod @classmethod
def set_name(cls, element, name): def set_name(cls, element: ifcopenshell.entity_instance, name: str) -> None:
element.Name = name element.Name = name
@classmethod @classmethod
def show_decorations(cls): def show_decorations(cls) -> None:
bpy.context.scene.DocProperties.should_draw_decorations = True bpy.context.scene.DocProperties.should_draw_decorations = True
@classmethod @classmethod
@@ -1013,15 +1032,15 @@ class Drawing(blenderbim.core.tool.Drawing):
# TODO below this point is highly experimental prototype code with no tests # TODO below this point is highly experimental prototype code with no tests
@classmethod @classmethod
def does_file_exist(cls, uri): def does_file_exist(cls, uri: str) -> bool:
return os.path.exists(uri) return os.path.exists(uri)
@classmethod @classmethod
def delete_file(cls, uri): def delete_file(cls, uri: str) -> None:
os.remove(uri) os.remove(uri)
@classmethod @classmethod
def move_file(cls, src, dest): def move_file(cls, src: str, dest: str) -> None:
try: try:
shutil.move(src, dest) shutil.move(src, dest)
except: except:
@@ -1029,7 +1048,9 @@ class Drawing(blenderbim.core.tool.Drawing):
shutil.copy(src, dest) shutil.copy(src, dest)
@classmethod @classmethod
def generate_drawing_name(cls, target_view, location_hint): def generate_drawing_name(
cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str
) -> str:
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint: if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint:
location = tool.Ifc.get().by_id(location_hint) location = tool.Ifc.get().by_id(location_hint)
if target_view == "REFLECTED_PLAN_VIEW": if target_view == "REFLECTED_PLAN_VIEW":
@@ -1042,7 +1063,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return target_view return target_view
@classmethod @classmethod
def get_default_layout_path(cls, identification, name): def get_default_layout_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
layouts_dir = ( layouts_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir")
@@ -1051,7 +1072,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_sheet_path(cls, identification, name): def get_default_sheet_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
sheets_dir = ( sheets_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir")
@@ -1060,7 +1081,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/") return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_titleblock_path(cls, name): def get_default_titleblock_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ( titleblocks_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
@@ -1069,7 +1090,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod @classmethod
def get_default_drawing_path(cls, name): def get_default_drawing_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
drawings_dir = ( drawings_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir")
@@ -1078,11 +1099,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/") return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod @classmethod
def sanitise_filename(cls, name): def sanitise_filename(cls, name: str) -> str:
return "".join(x for x in name if (x.isalnum() or x in "._- ")) return "".join(x for x in name if (x.isalnum() or x in "._- "))
@classmethod @classmethod
def get_default_drawing_resource_path(cls, resource): def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
project = tool.Ifc.get().by_type("IfcProject")[0] project = tool.Ifc.get().by_type("IfcProject")[0]
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr( resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
bpy.context.scene.DocProperties, f"{resource.lower()}_path" bpy.context.scene.DocProperties, f"{resource.lower()}_path"
@@ -1091,12 +1112,12 @@ class Drawing(blenderbim.core.tool.Drawing):
return resource_path.replace("\\", "/") return resource_path.replace("\\", "/")
@classmethod @classmethod
def get_default_shading_style(cls): def get_default_shading_style(cls) -> str:
dprops = bpy.context.scene.DocProperties dprops = bpy.context.scene.DocProperties
return dprops.shadingstyle_default return dprops.shadingstyle_default
@classmethod @classmethod
def setup_shading_styles_path(cls, resource_path): def setup_shading_styles_path(cls, resource_path: str) -> None:
resource_path = tool.Ifc.resolve_uri(resource_path) resource_path = tool.Ifc.resolve_uri(resource_path)
os.makedirs(os.path.dirname(resource_path), exist_ok=True) os.makedirs(os.path.dirname(resource_path), exist_ok=True)
if not os.path.exists(resource_path): if not os.path.exists(resource_path):
@@ -1387,7 +1408,7 @@ class Drawing(blenderbim.core.tool.Drawing):
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW") elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def clone(src): def clone(src: bpy.types.Object) -> bpy.types.Object:
dst = src.copy() dst = src.copy()
dst.data = dst.data.copy() dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "") dst.name = dst.name.replace("IfcGridAxis/", "")
@@ -1395,13 +1416,13 @@ class Drawing(blenderbim.core.tool.Drawing):
dst.data.BIMMeshProperties.ifc_definition_id = 0 dst.data.BIMMeshProperties.ifc_definition_id = 0
return dst return dst
def disassemble(obj): def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]:
mesh = bmesh.new() mesh = bmesh.new()
mesh.verts.ensure_lookup_table() mesh.verts.ensure_lookup_table()
mesh.from_mesh(obj.data) mesh.from_mesh(obj.data)
return obj, mesh return obj, mesh
def assemble(obj, mesh): def assemble(obj: bpy.types.Object, mesh: bmesh.types.BMesh) -> bpy.types.Object:
mesh.to_mesh(obj.data) mesh.to_mesh(obj.data)
return obj return obj
@@ -1413,7 +1434,7 @@ class Drawing(blenderbim.core.tool.Drawing):
obj.matrix_world.translation += annotation_offset obj.matrix_world.translation += annotation_offset
return obj, mesh return obj, mesh
def clip_to_camera_boundary(mesh): def clip_to_camera_boundary(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh:
mesh.verts.ensure_lookup_table() mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]] points = [v.co for v in mesh.verts[0:2]]
points = helper.clip_segment(bounds, points) points = helper.clip_segment(bounds, points)
@@ -1423,7 +1444,7 @@ class Drawing(blenderbim.core.tool.Drawing):
mesh.verts[1].co = points[1] mesh.verts[1].co = points[1]
return mesh return mesh
def draw_grids_vertically(mesh): def draw_grids_vertically(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh:
mesh.verts.ensure_lookup_table() mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]] points = [v.co for v in mesh.verts[0:2]]
points = helper.elevate_segment(bounds, points) points = helper.elevate_segment(bounds, points)
@@ -1534,11 +1555,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return text return text
@classmethod @classmethod
def sync_object_representation(cls, obj): def sync_object_representation(cls, obj: bpy.types.Object) -> None:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
@classmethod @classmethod
def sync_object_placement(cls, obj): def sync_object_placement(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
blender_matrix = np.array(obj.matrix_world) blender_matrix = np.array(obj.matrix_world)
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if (obj.scale - mathutils.Vector((1.0, 1.0, 1.0))).length > 1e-4: if (obj.scale - mathutils.Vector((1.0, 1.0, 1.0))).length > 1e-4:
@@ -1552,7 +1573,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return element return element
@classmethod @classmethod
def sync_grid_axis_object_placement(cls, obj, element): def sync_grid_axis_object_placement(cls, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0] grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid) grid_obj = tool.Ifc.get_object(grid)
if grid_obj: if grid_obj:
@@ -1568,11 +1589,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return document.HasDocumentReferences or [] return document.HasDocumentReferences or []
@classmethod @classmethod
def get_references_with_location(cls, location): def get_references_with_location(cls, location: Union[str, None]) -> list[ifcopenshell.entity_instance]:
return [r for r in tool.Ifc.get().by_type("IfcDocumentReference") if r.Location == location] return [r for r in tool.Ifc.get().by_type("IfcDocumentReference") if r.Location == location]
@classmethod @classmethod
def update_embedded_svg_location(cls, uri, reference, new_location): def update_embedded_svg_location(cls, uri: str, reference: ifcopenshell.entity_instance, new_location: str) -> None:
tree = etree.parse(uri) tree = etree.parse(uri)
root = tree.getroot() root = tree.getroot()
rel_location = os.path.relpath(new_location, os.path.dirname(uri)) rel_location = os.path.relpath(new_location, os.path.dirname(uri))
@@ -1612,11 +1633,13 @@ class Drawing(blenderbim.core.tool.Drawing):
return attributes return attributes
@classmethod @classmethod
def get_reference_location(cls, reference): def get_reference_location(cls, reference: ifcopenshell.entity_instance) -> Union[str, None]:
return reference.Location return reference.Location
@classmethod @classmethod
def get_reference_element(cls, reference): def get_reference_element(
cls, reference: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
refs = [r for r in tool.Ifc.get().by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference] refs = [r for r in tool.Ifc.get().by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference]
else: else:
@@ -1625,12 +1648,12 @@ class Drawing(blenderbim.core.tool.Drawing):
return refs[0].RelatedObjects[0] return refs[0].RelatedObjects[0]
@classmethod @classmethod
def get_drawing_human_scale(cls, drawing): def get_drawing_human_scale(cls, drawing: ifcopenshell.entity_instance) -> str:
pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") or {} pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") or {}
return "NTS" if pset.get("IsNTS", False) else pset.get("HumanScale", "NTS") return "NTS" if pset.get("IsNTS", False) else pset.get("HumanScale", "NTS")
@classmethod @classmethod
def get_drawing_metadata(cls, drawing): def get_drawing_metadata(cls, drawing: ifcopenshell.entity_instance) -> list[str]:
# fmt: off # fmt: off
return [ return [
v.strip() v.strip()
@@ -1642,7 +1665,7 @@ class Drawing(blenderbim.core.tool.Drawing):
# fmt: on # fmt: on
@classmethod @classmethod
def get_annotation_z_index(cls, drawing): def get_annotation_z_index(cls, drawing: ifcopenshell.entity_instance) -> float:
return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0 return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0
@classmethod @classmethod
@@ -1654,11 +1677,11 @@ class Drawing(blenderbim.core.tool.Drawing):
return symbol return symbol
@classmethod @classmethod
def has_linework(cls, drawing): def has_linework(cls, drawing: ifcopenshell.entity_instance) -> bool:
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasLinework", False) return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasLinework", False)
@classmethod @classmethod
def has_annotation(cls, drawing): def has_annotation(cls, drawing: ifcopenshell.entity_instance) -> bool:
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False) return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False)
@classmethod @classmethod
@@ -1723,19 +1746,21 @@ class Drawing(blenderbim.core.tool.Drawing):
return elements return elements
@classmethod @classmethod
def get_annotation_element(cls, element): def get_annotation_element(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
for rel in element.HasAssignments: for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"): if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct return rel.RelatingProduct
@classmethod @classmethod
def get_drawing_reference(cls, drawing): def get_drawing_reference(cls, drawing: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
for rel in drawing.HasAssociations: for rel in drawing.HasAssociations:
if rel.is_a("IfcRelAssociatesDocument"): if rel.is_a("IfcRelAssociatesDocument"):
return rel.RelatingDocument return rel.RelatingDocument
@classmethod @classmethod
def get_reference_document(cls, reference): def get_reference_document(
cls, reference: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
return reference.ReferenceToDocument[0] return reference.ReferenceToDocument[0]
return reference.ReferencedDocument return reference.ReferencedDocument
@@ -1749,13 +1774,13 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Ifc.get_object(product).select_set(True) tool.Ifc.get_object(product).select_set(True)
@classmethod @classmethod
def is_drawing_active(cls): def is_drawing_active(cls) -> bool:
camera = bpy.context.scene.camera camera = bpy.context.scene.camera
area = tool.Blender.get_view3d_area() area = tool.Blender.get_view3d_area()
return camera and camera.type == "CAMERA" and camera.BIMObjectProperties.ifc_definition_id and area return camera and camera.type == "CAMERA" and camera.BIMObjectProperties.ifc_definition_id and area
@classmethod @classmethod
def is_camera_orthographic(cls): def is_camera_orthographic(cls) -> bool:
camera = bpy.context.scene.camera camera = bpy.context.scene.camera
return True if (camera and camera.data.type == "ORTHO") else False return True if (camera and camera.data.type == "ORTHO") else False
@@ -1764,7 +1789,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
@classmethod @classmethod
def run_drawing_activate_model(cls): def run_drawing_activate_model(cls) -> None:
bpy.ops.bim.activate_model() bpy.ops.bim.activate_model()
@classmethod @classmethod
@@ -1906,7 +1931,15 @@ class Drawing(blenderbim.core.tool.Drawing):
) )
@classmethod @classmethod
def is_in_camera_view(cls, obj, camera_inverse_matrix, x, y, clip_start, clip_end): def is_in_camera_view(
cls,
obj: bpy.types.Object,
camera_inverse_matrix: Matrix,
x: float,
y: float,
clip_start: float,
clip_end: float,
) -> bool:
local_bbox = [camera_inverse_matrix @ obj.matrix_world @ Vector(v) for v in obj.bound_box] local_bbox = [camera_inverse_matrix @ obj.matrix_world @ Vector(v) for v in obj.bound_box]
local_x = [v.x for v in local_bbox] local_x = [v.x for v in local_bbox]
local_y = [v.y for v in local_bbox] local_y = [v.y for v in local_bbox]
@@ -1921,14 +1954,14 @@ class Drawing(blenderbim.core.tool.Drawing):
return True return True
@classmethod @classmethod
def is_intersecting_camera(cls, obj, camera): def is_intersecting_camera(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> bool:
# Based on separating axis theorem # Based on separating axis theorem
plane_co = camera.matrix_world.translation plane_co = camera.matrix_world.translation
plane_no = camera.matrix_world.col[2].xyz plane_no = camera.matrix_world.col[2].xyz
return cls.is_intersecting_plane(obj, plane_co, plane_no) return cls.is_intersecting_plane(obj, plane_co, plane_no)
@classmethod @classmethod
def is_intersecting_plane(cls, obj, plane_co, plane_no): def is_intersecting_plane(cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector) -> bool:
# Broadphase check using the bounding box # Broadphase check using the bounding box
bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box] bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box]
bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords] bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords]
@@ -1958,7 +1991,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return pos_exists and neg_exists return pos_exists and neg_exists
@classmethod @classmethod
def bisect_mesh(cls, obj, camera): def bisect_mesh(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> tuple[list[Vector], list[list[int]]]:
camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world
plane_co = camera_matrix.translation plane_co = camera_matrix.translation
plane_no = camera_matrix.col[2].xyz plane_no = camera_matrix.col[2].xyz
@@ -1969,7 +2002,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return cls.bisect_mesh_with_plane(obj, plane_co, plane_no, global_offset=global_offset) return cls.bisect_mesh_with_plane(obj, plane_co, plane_no, global_offset=global_offset)
@classmethod @classmethod
def bisect_mesh_with_plane(cls, obj, plane_co, plane_no, global_offset=None): def bisect_mesh_with_plane(
cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector, global_offset: Optional[Vector] = None
) -> tuple[list[Vector], list[list[int]]]:
if global_offset is None: if global_offset is None:
global_offset = Vector() global_offset = Vector()
@@ -1980,9 +2015,9 @@ class Drawing(blenderbim.core.tool.Drawing):
geom = bm.verts[:] + bm.edges[:] + bm.faces[:] geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no) results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no)
vert_map = {} vert_map: dict[int, int] = {}
verts = [] verts: list[Vector] = []
edges = [] edges: list[list[int]] = []
i = 0 i = 0
for geom in results["geom_cut"]: for geom in results["geom_cut"]:
if isinstance(geom, bmesh.types.BMVert): if isinstance(geom, bmesh.types.BMVert):
@@ -1998,12 +2033,12 @@ class Drawing(blenderbim.core.tool.Drawing):
return verts, edges return verts, edges
@classmethod @classmethod
def get_scale_ratio(cls, scale): def get_scale_ratio(cls, scale: str) -> float:
numerator, denominator = scale.split("/") numerator, denominator = scale.split("/")
return float(numerator) / float(denominator) return float(numerator) / float(denominator)
@classmethod @classmethod
def get_diagram_scale(cls, obj): def get_diagram_scale(cls, obj: bpy.types.Object) -> dict[str, float]:
props = obj.data.BIMCameraProperties props = obj.data.BIMCameraProperties
scale = props.diagram_scale scale = props.diagram_scale
if scale != "CUSTOM": if scale != "CUSTOM":
@@ -2029,7 +2064,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return {"HumanScale": human_scale, "Scale": scale} return {"HumanScale": human_scale, "Scale": scale}
@classmethod @classmethod
def convert_scale_string(cls, value): def convert_scale_string(cls, value: str) -> float:
try: try:
return float(value) return float(value)
except: except:
@@ -2060,7 +2095,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return result * 0.0254 return result * 0.0254
@classmethod @classmethod
def extend_line(cls, start, end, distance): def extend_line(cls, start: Vector, end: Vector, distance: float) -> tuple[list[float], list[float]]:
start = np.array(start) start = np.array(start)
end = np.array(end) end = np.array(end)
direction = end - start direction = end - start
@@ -2068,8 +2103,8 @@ class Drawing(blenderbim.core.tool.Drawing):
return (start - offset).tolist(), (end + offset).tolist() return (start - offset).tolist(), (end + offset).tolist()
@classmethod @classmethod
def get_sheet_references(cls, drawing): def get_sheet_references(cls, drawing: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
sheet_references = [] sheet_references: list[ifcopenshell.entity_instance] = []
drawing_reference = cls.get_drawing_document(drawing) drawing_reference = cls.get_drawing_document(drawing)
for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"): for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"):
if not sheet.Scope == "SHEET": if not sheet.Scope == "SHEET":
@@ -2082,7 +2117,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return sheet_references return sheet_references
@classmethod @classmethod
def get_camera_matrix(cls, camera): def get_camera_matrix(cls, camera: bpy.types.Object) -> Matrix:
matrix_world = camera.matrix_world.copy().normalized() matrix_world = camera.matrix_world.copy().normalized()
location, rotation, scale = matrix_world.decompose() location, rotation, scale = matrix_world.decompose()
if scale.x < 0 or scale.y < 0 or scale.z < 0: if scale.x < 0 or scale.y < 0 or scale.z < 0:
@@ -21,6 +21,9 @@ import json
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.api.georeference import ifcopenshell.api.georeference
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.helper import blenderbim.bim.helper
+62 -37
View File
@@ -1,3 +1,22 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os import os
import re import re
import bpy import bpy
@@ -5,6 +24,7 @@ import logging
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool import blenderbim.tool as tool
from typing import TYPE_CHECKING, Union
# allows git import even if git executable isn't found # allows git import even if git executable isn't found
os.environ["GIT_PYTHON_REFRESH"] = "quiet" os.environ["GIT_PYTHON_REFRESH"] = "quiet"
@@ -13,15 +33,20 @@ try:
except ImportError: except ImportError:
print("Warning: GitPython not available.") print("Warning: GitPython not available.")
if TYPE_CHECKING:
import git
class IfcGit: class IfcGit:
STEP_IDS = dict[str, set[int]]
@classmethod @classmethod
def init_repo(cls, path_dir): def init_repo(cls, path_dir: str) -> None:
IfcGitRepo.repo = git.Repo.init(path_dir) IfcGitRepo.repo = git.Repo.init(path_dir)
cls.config_info_attributes(IfcGitRepo.repo) cls.config_info_attributes(IfcGitRepo.repo)
@classmethod @classmethod
def clone_repo(cls, remote_url, local_folder): def clone_repo(cls, remote_url: str, local_folder: str) -> git.Repo:
IfcGitRepo.repo = git.Repo.clone_from( IfcGitRepo.repo = git.Repo.clone_from(
url=remote_url, url=remote_url,
to_path=local_folder, to_path=local_folder,
@@ -30,7 +55,7 @@ class IfcGit:
return IfcGitRepo.repo return IfcGitRepo.repo
@classmethod @classmethod
def load_anyifc(cls, repo): def load_anyifc(cls, repo: git.Repo) -> bool:
working_dir = repo.working_dir working_dir = repo.working_dir
for item in os.listdir(working_dir): for item in os.listdir(working_dir):
path = os.path.join(working_dir, item) path = os.path.join(working_dir, item)
@@ -40,11 +65,11 @@ class IfcGit:
return False return False
@classmethod @classmethod
def get_path_dir(cls, path_ifc): def get_path_dir(cls, path_ifc: str) -> str:
return os.path.abspath(os.path.dirname(path_ifc)) return os.path.abspath(os.path.dirname(path_ifc))
@classmethod @classmethod
def repo_from_path(cls, path): def repo_from_path(cls, path: str) -> Union[git.Repo, None]:
"""Returns a Git repository object or None""" """Returns a Git repository object or None"""
if os.path.isdir(path): if os.path.isdir(path):
@@ -72,7 +97,7 @@ class IfcGit:
return repo return repo
@classmethod @classmethod
def add_file_to_repo(cls, repo, path_file): def add_file_to_repo(cls, repo: git.Repo, path_file: str) -> None:
if os.name == "nt": if os.name == "nt":
cls.dos2unix(path_file) cls.dos2unix(path_file)
repo.index.add(path_file) repo.index.add(path_file)
@@ -80,11 +105,11 @@ class IfcGit:
bpy.ops.ifcgit.refresh() bpy.ops.ifcgit.refresh()
@classmethod @classmethod
def git_checkout(cls, path_file): def git_checkout(cls, path_file: str) -> None:
IfcGitRepo.repo.git.checkout(path_file) IfcGitRepo.repo.git.checkout(path_file)
@classmethod @classmethod
def checkout_new_branch(cls, path_file): def checkout_new_branch(cls, path_file: str) -> None:
"""Create a branch and move uncommitted changes to this branch""" """Create a branch and move uncommitted changes to this branch"""
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
if props.new_branch_name: if props.new_branch_name:
@@ -94,7 +119,7 @@ class IfcGit:
bpy.ops.ifcgit.refresh() bpy.ops.ifcgit.refresh()
@classmethod @classmethod
def git_commit(cls, path_file): def git_commit(cls, path_file: str) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo repo = IfcGitRepo.repo
if os.name == "nt": if os.name == "nt":
@@ -104,7 +129,7 @@ class IfcGit:
props.commit_message = "" props.commit_message = ""
@classmethod @classmethod
def add_tag(cls, repo): def add_tag(cls, repo: git.Repo) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
item = props.ifcgit_commits[props.commit_index] item = props.ifcgit_commits[props.commit_index]
repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message) repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message)
@@ -112,19 +137,19 @@ class IfcGit:
props.new_tag_message = "" props.new_tag_message = ""
@classmethod @classmethod
def delete_tag(cls, repo, tag_name): def delete_tag(cls, repo: git.Repo, tag_name: git.TagReference) -> None:
if tag_name in repo.tags: if tag_name in repo.tags:
repo.delete_tag(tag_name) repo.delete_tag(tag_name)
@classmethod @classmethod
def add_remote(cls, repo): def add_remote(cls, repo: git.Repo) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo.create_remote(name=props.remote_name, url=props.remote_url) repo.create_remote(name=props.remote_name, url=props.remote_url)
props.remote_name = "" props.remote_name = ""
props.remote_url = "" props.remote_url = ""
@classmethod @classmethod
def delete_remote(cls, repo): def delete_remote(cls, repo: git.Repo) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
remote_name = props.select_remote remote_name = props.select_remote
if remote_name in repo.remotes: if remote_name in repo.remotes:
@@ -133,7 +158,7 @@ class IfcGit:
props.select_remote = repo.remotes[0].name props.select_remote = repo.remotes[0].name
@classmethod @classmethod
def push(cls, repo, remote_name, branch_name): def push(cls, repo: git.Repo, remote_name: str, branch_name: str) -> Union[str, None]:
cls.config_push(repo) cls.config_push(repo)
remote = repo.remotes[remote_name] remote = repo.remotes[remote_name]
try: try:
@@ -142,7 +167,7 @@ class IfcGit:
return exc.stderr return exc.stderr
@classmethod @classmethod
def create_new_branch(cls): def create_new_branch(cls) -> None:
"""Convert a detached HEAD into a branch""" """Convert a detached HEAD into a branch"""
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo repo = IfcGitRepo.repo
@@ -154,7 +179,7 @@ class IfcGit:
bpy.ops.ifcgit.refresh() bpy.ops.ifcgit.refresh()
@classmethod @classmethod
def clear_commits_list(cls): def clear_commits_list(cls) -> None:
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "MATERIAL" area.spaces[0].shading.color_type = "MATERIAL"
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
@@ -163,7 +188,7 @@ class IfcGit:
props.ifcgit_commits.clear() props.ifcgit_commits.clear()
@classmethod @classmethod
def get_commits_list(cls, path_ifc, lookup): def get_commits_list(cls, path_ifc: str, lookup: dict[str, Any]) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo = cls.repo_from_path(path_ifc) repo = cls.repo_from_path(path_ifc)
@@ -205,14 +230,14 @@ class IfcGit:
list_item.tags[-1].message = tag.tag.message list_item.tags[-1].message = tag.tag.message
@classmethod @classmethod
def refresh_revision_list(cls, path_ifc): def refresh_revision_list(cls, path_ifc: str) -> None:
repo = cls.repo_from_path(path_ifc) repo = cls.repo_from_path(path_ifc)
cls.clear_commits_list() cls.clear_commits_list()
lookup = cls.tags_by_hexsha(repo) lookup = cls.tags_by_hexsha(repo)
cls.get_commits_list(path_ifc, lookup) cls.get_commits_list(path_ifc, lookup)
@classmethod @classmethod
def is_valid_ref_format(cls, string): def is_valid_ref_format(cls, string: str) -> Union[re.Match[str], None]:
"""Check a bare branch or tag name is valid""" """Check a bare branch or tag name is valid"""
return re.match( return re.match(
@@ -221,7 +246,7 @@ class IfcGit:
) )
@classmethod @classmethod
def load_project(cls, path_ifc=""): def load_project(cls, path_ifc: str = "") -> None:
"""Clear and load an ifc project""" """Clear and load an ifc project"""
if path_ifc: if path_ifc:
@@ -248,7 +273,7 @@ class IfcGit:
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
@classmethod @classmethod
def branches_by_hexsha(cls, repo): def branches_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]:
"""reverse lookup for branches""" """reverse lookup for branches"""
result = {} result = {}
@@ -267,7 +292,7 @@ class IfcGit:
return result return result
@classmethod @classmethod
def tags_by_hexsha(cls, repo): def tags_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]:
"""reverse lookup for tags""" """reverse lookup for tags"""
result = {} result = {}
@@ -279,7 +304,7 @@ class IfcGit:
return result return result
@classmethod @classmethod
def ifc_diff_ids(cls, repo, hash_a, hash_b, path_ifc): def ifc_diff_ids(cls, repo: git.Repo, hash_a: str, hash_b: str, path_ifc: str) -> STEP_IDS:
"""Given two revision hashes and a filename, retrieve""" """Given two revision hashes and a filename, retrieve"""
"""step-ids of modified, added and removed entities""" """step-ids of modified, added and removed entities"""
@@ -309,7 +334,7 @@ class IfcGit:
} }
@classmethod @classmethod
def get_revisions_step_ids(cls): def get_revisions_step_ids(cls) -> Union[STEP_IDS, None]:
path_ifc = bpy.data.scenes["Scene"].BIMProperties.ifc_file path_ifc = bpy.data.scenes["Scene"].BIMProperties.ifc_file
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
@@ -341,7 +366,7 @@ class IfcGit:
return step_ids return step_ids
@classmethod @classmethod
def get_modified_shape_object_step_ids(cls, step_ids): def get_modified_shape_object_step_ids(cls, step_ids: STEP_IDS) -> STEP_IDS:
model = tool.Ifc.get() model = tool.Ifc.get()
modified_shape_object_step_ids = {"modified": []} modified_shape_object_step_ids = {"modified": []}
@@ -353,7 +378,7 @@ class IfcGit:
return modified_shape_object_step_ids return modified_shape_object_step_ids
@classmethod @classmethod
def update_step_ids(cls, step_ids, modified_shape_object_step_ids): def update_step_ids(cls, step_ids: STEP_IDS, modified_shape_object_step_ids: STEP_IDS) -> STEP_IDS:
final_step_ids = {} final_step_ids = {}
final_step_ids["added"] = step_ids["added"] final_step_ids["added"] = step_ids["added"]
@@ -362,7 +387,7 @@ class IfcGit:
return final_step_ids return final_step_ids
@classmethod @classmethod
def colourise(cls, step_ids): def colourise(cls, step_ids: STEP_IDS) -> None:
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT" area.spaces[0].shading.color_type = "OBJECT"
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
@@ -384,7 +409,7 @@ class IfcGit:
obj.color = (1.0, 1.0, 1.0, 0.5) obj.color = (1.0, 1.0, 1.0, 0.5)
@classmethod @classmethod
def switch_to_revision_item(cls): def switch_to_revision_item(cls) -> None:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index] item = props.ifcgit_commits[props.commit_index]
@@ -399,13 +424,13 @@ class IfcGit:
repo.git.checkout(item.hexsha) repo.git.checkout(item.hexsha)
@classmethod @classmethod
def delete_collection(cls, blender_collection): def delete_collection(cls, blender_collection: bpy.types.Collection) -> None:
for obj in blender_collection.objects: for obj in blender_collection.objects:
bpy.data.objects.remove(obj, do_unlink=True) bpy.data.objects.remove(obj, do_unlink=True)
bpy.data.collections.remove(blender_collection) bpy.data.collections.remove(blender_collection)
@classmethod @classmethod
def is_valid_branch_name(cls, new_branch_name): def is_valid_branch_name(cls, new_branch_name: str):
"""Check if a branch name is valid and doesn't conflict with existing branches""" """Check if a branch name is valid and doesn't conflict with existing branches"""
if not cls.is_valid_ref_format(new_branch_name): if not cls.is_valid_ref_format(new_branch_name):
return False return False
@@ -414,7 +439,7 @@ class IfcGit:
return True return True
@classmethod @classmethod
def config_ifcmerge(cls): def config_ifcmerge(cls) -> None:
config_reader = IfcGitRepo.repo.config_reader() config_reader = IfcGitRepo.repo.config_reader()
section = 'mergetool "ifcmerge"' section = 'mergetool "ifcmerge"'
if not config_reader.has_section(section): if not config_reader.has_section(section):
@@ -428,7 +453,7 @@ class IfcGit:
config_writer.set_value(section, "trustExitCode", True) config_writer.set_value(section, "trustExitCode", True)
@classmethod @classmethod
def config_push(cls, repo): def config_push(cls, repo: git.Repo) -> None:
"""Set push.autoSetupRemote""" """Set push.autoSetupRemote"""
config_reader = repo.config_reader() config_reader = repo.config_reader()
if not config_reader.has_section("push"): if not config_reader.has_section("push"):
@@ -437,7 +462,7 @@ class IfcGit:
config_writer.set_value("push", "autoSetupRemote", True) config_writer.set_value("push", "autoSetupRemote", True)
@classmethod @classmethod
def config_info_attributes(cls, repo): def config_info_attributes(cls, repo: git.Repo) -> None:
"""Set IFC files as text in .git/info/attributes""" """Set IFC files as text in .git/info/attributes"""
path_attributes = os.path.join(repo.git_dir, "info", "attributes") path_attributes = os.path.join(repo.git_dir, "info", "attributes")
if not os.path.exists(path_attributes): if not os.path.exists(path_attributes):
@@ -446,7 +471,7 @@ class IfcGit:
f.write("*.ifc text") f.write("*.ifc text")
@classmethod @classmethod
def dos2unix(cls, path_file): def dos2unix(cls, path_file: str) -> None:
with open(path_file, "rb") as infile: with open(path_file, "rb") as infile:
content = infile.read() content = infile.read()
with open(path_file, "wb") as output: with open(path_file, "wb") as output:
@@ -454,7 +479,7 @@ class IfcGit:
output.write(line + b"\n") output.write(line + b"\n")
@classmethod @classmethod
def execute_merge(cls, path_ifc, operator): def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, False]:
props = bpy.context.scene.IfcGitProperties props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index] item = props.ifcgit_commits[props.commit_index]
@@ -498,7 +523,7 @@ class IfcGit:
cls.refresh_revision_list(path_ifc) cls.refresh_revision_list(path_ifc)
@classmethod @classmethod
def entity_log(cls, path_ifc, step_id): def entity_log(cls, path_ifc: str, step_id: int) -> str:
"""Raw git log for this entity""" """Raw git log for this entity"""
repo = IfcGitRepo.repo repo = IfcGitRepo.repo
if not repo: if not repo:
@@ -514,4 +539,4 @@ class IfcGit:
class IfcGitRepo: class IfcGitRepo:
repo = None repo: git.Repo = None
+18 -8
View File
@@ -392,10 +392,11 @@ class Spatial(blenderbim.core.tool.Spatial):
continue continue
old_mesh = obj.data old_mesh = obj.data
assert isinstance(old_mesh, bpy.types.Mesh)
if visible_element.HasOpenings: if visible_element.HasOpenings:
new_mesh = cls.get_gross_mesh_from_element(visible_element) new_mesh = cls.get_gross_mesh_from_element(visible_element)
else: else:
new_mesh = obj.data.copy() new_mesh = old_mesh.copy()
obj.data = new_mesh obj.data = new_mesh
# Boundary objects are likely triangulated. If a triangulated quad # Boundary objects are likely triangulated. If a triangulated quad
@@ -463,15 +464,15 @@ class Spatial(blenderbim.core.tool.Spatial):
return mesh return mesh
@classmethod @classmethod
def get_x_y_z_h_mat_from_active_obj(cls, active_obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]: def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]:
mat = active_obj.matrix_world mat = obj.matrix_world
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector()) local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center global_bbox_center = mat @ local_bbox_center
x = global_bbox_center.x x = global_bbox_center.x
y = global_bbox_center.y y = global_bbox_center.y
z = (mat @ Vector(active_obj.bound_box[0])).z z = (mat @ Vector(obj.bound_box[0])).z
h = active_obj.dimensions.z h = obj.dimensions.z
return x, y, z, h, mat return x, y, z, h, mat
@classmethod @classmethod
@@ -490,7 +491,11 @@ class Spatial(blenderbim.core.tool.Spatial):
boundary_elements = cls.get_boundary_elements(selected_objects) boundary_elements = cls.get_boundary_elements(selected_objects)
polys = cls.get_polygons(boundary_elements) polys = cls.get_polygons(boundary_elements)
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2) union = shapely.ops.unary_union(polys).buffer(
converted_tolerance,
cap_style=shapely.constructive.BufferCapStyle.flat,
join_style=shapely.constructive.BufferJoinStyle.mitre,
)
union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=0.1)) union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=0.1))
return union return union
@@ -576,7 +581,12 @@ class Spatial(blenderbim.core.tool.Spatial):
def get_buffered_poly_from_linear_ring(cls, linear_ring: shapely.LinearRing) -> Polygon: def get_buffered_poly_from_linear_ring(cls, linear_ring: shapely.LinearRing) -> Polygon:
poly = Polygon(linear_ring) poly = Polygon(linear_ring)
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2) poly = poly.buffer(
converted_tolerance,
single_sided=True,
cap_style=shapely.BufferCapStyle.flat,
join_style=shapely.BufferJoinStyle.mitre,
)
return poly return poly
@classmethod @classmethod
@@ -17,14 +17,15 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
from typing import Optional, Literal import ifcopenshell.util.representation
from typing import Optional
def add_context( def add_context(
file: ifcopenshell.file, file: ifcopenshell.file,
context_type: Optional[Literal["Model", "Plan"]] = None, context_type: Optional[ifcopenshell.util.representation.CONTEXT_TYPE] = None,
context_identifier: Optional[str] = None, context_identifier: Optional[ifcopenshell.util.representation.REPRESENTATION_IDENTIFIER] = None,
target_view: Optional[str] = None, target_view: Optional[ifcopenshell.util.representation.TARGET_VIEW] = None,
parent: Optional[ifcopenshell.entity_instance] = None, parent: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
"""Adds a new geometric representation context """Adds a new geometric representation context
@@ -106,7 +107,6 @@ def add_context(
the common target views above or consult the IFC documentation the common target views above or consult the IFC documentation
(under the IfcShapeRepresentation page) for more details. Optional (under the IfcShapeRepresentation page) for more details. Optional
for contexts, but mandatory for subcontexts. for contexts, but mandatory for subcontexts.
:type target_view: str, optional
:param parent: the parent context. Must be left as None (the default) :param parent: the parent context. Must be left as None (the default)
for contexts, and only set for subcontexts. Note that there are only for contexts, and only set for subcontexts. Note that there are only
contexts and subcontexts, a subcontext cannot have any children. contexts and subcontexts, a subcontext cannot have any children.
+1
View File
@@ -26,6 +26,7 @@ import typing
import inspect import inspect
import collections import collections
import importlib import importlib
import importlib.util
from typing import Union from typing import Union
+6
View File
@@ -18,8 +18,14 @@
import ifcpatch import ifcpatch
import ifcopenshell import ifcopenshell
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.georeference import ifcopenshell.api.georeference
import ifcopenshell.geom
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape
import ifcopenshell.util.shape_builder import ifcopenshell.util.shape_builder
import test.bootstrap import test.bootstrap
import tempfile import tempfile