This commit is contained in:
Andrej730
2025-03-31 13:56:37 +05:00
parent 547506d626
commit e3228513c0
6 changed files with 64 additions and 42 deletions
@@ -982,14 +982,17 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
self.set_boundary_name(parent_boundary) self.set_boundary_name(parent_boundary)
return parent_boundary return parent_boundary
def get_face_matrix(self, p1, p2, p3): def get_face_matrix(self, p1: Vector, p2: Vector, p3: Vector) -> Matrix:
edge1 = p2 - p1 edge1 = p2 - p1
edge2 = p3 - p1 edge2 = p3 - p1
normal = edge1.cross(edge2) normal = edge1.cross(edge2)
assert isinstance(normal, Vector)
z_axis = normal.normalized() z_axis = normal.normalized()
x_axis = p2 - p1 x_axis = p2 - p1
x_axis.normalize() x_axis.normalize()
y_axis = z_axis.cross(x_axis) y_axis = z_axis.cross(x_axis)
assert isinstance(y_axis, Vector)
mat = Matrix() mat = Matrix()
mat.col[0][:3] = x_axis mat.col[0][:3] = x_axis
@@ -1059,7 +1062,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
return surface return surface
def set_boundary_name(self, boundary): def set_boundary_name(self, boundary: ifcopenshell.entity_instance):
""" """
By convention 1stLevel and 2ndLevel boundary have specific name and description By convention 1stLevel and 2ndLevel boundary have specific name and description
See https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRelSpaceBoundary.htm See https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRelSpaceBoundary.htm
@@ -24,7 +24,7 @@ import bonsai.tool as tool
from bpy_extras.io_utils import ImportHelper, ExportHelper from bpy_extras.io_utils import ImportHelper, ExportHelper
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.cost as core import bonsai.core.cost as core
from typing import get_args, TYPE_CHECKING from typing import get_args, TYPE_CHECKING, Literal
class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator): class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
@@ -695,6 +695,9 @@ class ExportCostSchedules(bpy.types.Operator, ExportHelper):
default=True, default=True,
) )
if TYPE_CHECKING:
format: Literal["CSV", "XLSX", "ODS"]
@property @property
def filename_ext(self) -> str: def filename_ext(self) -> str:
return f".{self.format.lower()}" return f".{self.format.lower()}"
+20 -8
View File
@@ -24,11 +24,13 @@ import ifcopenshell.util.representation
import ifcopenshell.util.type import ifcopenshell.util.type
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.type
import bonsai.bim.helper import bonsai.bim.helper
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.geometry import bonsai.core.geometry
import bonsai.core.type as core import bonsai.core.type as core
import bonsai.core.root import bonsai.core.root
from typing import TYPE_CHECKING
class AssignType(bpy.types.Operator, tool.Ifc.Operator): class AssignType(bpy.types.Operator, tool.Ifc.Operator):
@@ -38,15 +40,18 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
relating_type: bpy.props.IntProperty() relating_type: bpy.props.IntProperty()
related_object: bpy.props.StringProperty() related_object: bpy.props.StringProperty()
if TYPE_CHECKING:
relating_type: int
related_object: str
def _execute(self, context): def _execute(self, context):
relating_type = tool.Ifc.get().by_id( relating_type = tool.Ifc.get().by_id(
self.relating_type or int(context.active_object.BIMTypeProperties.relating_type) self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
) )
related_objects = ( if self.related_object:
[bpy.data.objects.get(self.related_object)] related_objects = [bpy.data.objects[self.related_object]]
if self.related_object else:
else context.selected_objects or [context.active_object] related_objects = tool.Blender.get_selected_objects()
)
model_props = tool.Model.get_model_props() model_props = tool.Model.get_model_props()
for obj in related_objects: for obj in related_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
@@ -63,17 +68,24 @@ class UnassignType(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty() related_object: bpy.props.StringProperty()
if TYPE_CHECKING:
related_object: str
def _execute(self, context): def _execute(self, context):
def exclude_callback(attribute): def exclude_callback(attribute):
return attribute.is_a("IfcProfileDef") and attribute.ProfileName return attribute.is_a("IfcProfileDef") and attribute.ProfileName
self.file = tool.Ifc.get() self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects if self.related_object:
for obj in objs: related_objects = [bpy.data.objects[self.related_object]]
else:
related_objects = tool.Blender.get_selected_objects()
for obj in related_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcObject"): if not element or not element.is_a("IfcObject"):
continue continue
ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element]) ifcopenshell.api.type.unassign_type(self.file, related_objects=[element])
if element.Representation: if element.Representation:
new_active_representation = None new_active_representation = None
+4 -1
View File
@@ -337,7 +337,10 @@ def calculate_cost_item_resource_value(ifc: tool.Ifc, cost_item: ifcopenshell.en
def export_cost_schedules( def export_cost_schedules(
cost: tool.Cost, filepath: str, format: str, cost_schedule: Union[ifcopenshell.entity_instance, None] = None cost: tool.Cost,
filepath: str,
format: Literal["CSV", "ODS", "XLSX"],
cost_schedule: Union[ifcopenshell.entity_instance, None] = None,
) -> Union[str, None]: ) -> Union[str, None]:
cost.play_sound() cost.play_sound()
return cost.export_cost_schedules(filepath, format, cost_schedule) return cost.export_cost_schedules(filepath, format, cost_schedule)
+7 -1
View File
@@ -29,6 +29,7 @@ import bonsai.bim.helper
import json import json
from pathlib import Path from pathlib import Path
from typing import Optional, Any, Generator, Union, Literal, TYPE_CHECKING from typing import Optional, Any, Generator, Union, Literal, TYPE_CHECKING
from typing_extensions import assert_never
if TYPE_CHECKING: if TYPE_CHECKING:
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
@@ -652,7 +653,10 @@ class Cost(bonsai.core.tool.Cost):
@classmethod @classmethod
def export_cost_schedules( def export_cost_schedules(
cls, filepath: str, format: str, cost_schedule: Optional[ifcopenshell.entity_instance] = None cls,
filepath: str,
format: Literal["CSV", "ODS", "XLSX"],
cost_schedule: Optional[ifcopenshell.entity_instance] = None,
) -> Union[str, None]: ) -> Union[str, None]:
import subprocess import subprocess
import os import os
@@ -680,6 +684,8 @@ class Cost(bonsai.core.tool.Cost):
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule) writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule)
writer.write() writer.write()
else:
assert_never(format)
try: try:
if path: if path:
if sys.platform == "win32": if sys.platform == "win32":
@@ -92,18 +92,14 @@ def assign_type(
ambiguous, unknown or are so bespoke as to have no logical type. ambiguous, unknown or are so bespoke as to have no logical type.
:param related_objects: The IfcElement occurrences. :param related_objects: The IfcElement occurrences.
:type related_objects: list[ifcopenshell.entity_instance]
:param relating_type: The IfcElementType type. :param relating_type: The IfcElementType type.
:type relating_type: ifcopenshell.entity_instance
:param should_map_representations: If a type has a representation map, :param should_map_representations: If a type has a representation map,
IFC requires all occurrences to map those representations. Some IFC IFC requires all occurrences to map those representations. Some IFC
vendors might disobey this, or you might want to handle it vendors might disobey this, or you might want to handle it
yourusecase. In this scenario, you may set this to False. yourusecase. In this scenario, you may set this to False.
This also enabled adding material usages mapping. This also enabled adding material usages mapping.
:type should_map_representations: bool
:return: The IfcRelDefinesByType relationship :return: The IfcRelDefinesByType relationship
or `None` if `related_objects` was empty list. or `None` if `related_objects` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example: Example:
@@ -177,29 +173,26 @@ def assign_type(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = { return usecase.execute(related_objects, relating_type, should_map_representations)
"related_objects": related_objects,
"relating_type": relating_type,
"should_map_representations": should_map_representations,
}
return usecase.execute()
class Usecase: class Usecase:
file: ifcopenshell.file file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(
if not self.settings["related_objects"]: self,
related_objects: list[ifcopenshell.entity_instance],
relating_type: ifcopenshell.entity_instance,
should_map_representations: bool,
):
if not related_objects:
return return
related_objects = set(self.settings["related_objects"]) related_objects_set = set(related_objects)
relating_type = self.settings["relating_type"]
ifc2x3 = self.file.schema == "IFC2X3" ifc2x3 = self.file.schema == "IFC2X3"
related_objects = set(self.settings["related_objects"]) related_objects_set = set(related_objects)
relating_type = self.settings["relating_type"]
if ifc2x3: if ifc2x3:
types = next(iter(relating_type.ObjectTypeOf), None) types = next(iter(relating_type.ObjectTypeOf), None)
else: else:
@@ -210,20 +203,20 @@ class Usecase:
objects_with_types: list[ifcopenshell.entity_instance] = [] objects_with_types: list[ifcopenshell.entity_instance] = []
# check if there is anything to change # check if there is anything to change
for object in related_objects: for obj in related_objects_set:
if ifc2x3: if ifc2x3:
object_rel = next((i for i in object.IsDefinedBy if i.is_a("IfcRelDefinesByType")), None) object_rel = next((i for i in obj.IsDefinedBy if i.is_a("IfcRelDefinesByType")), None)
else: else:
object_rel = next(iter(object.IsTypedBy), None) object_rel = next(iter(obj.IsTypedBy), None)
if object_rel is None: if object_rel is None:
objects_without_types.append(object) objects_without_types.append(obj)
continue continue
# either rel doesn't exist or product is part of different rel # either rel doesn't exist or product is part of different rel
if object_rel != types: if object_rel != types:
previous_types_rels.add(object_rel) previous_types_rels.add(object_rel)
objects_with_types.append(object) objects_with_types.append(obj)
objects_to_change = objects_without_types + objects_with_types objects_to_change = objects_without_types + objects_with_types
# nothing to change # nothing to change
@@ -232,7 +225,7 @@ class Usecase:
# unassign from previous types # unassign from previous types
for is_typed_by in previous_types_rels: for is_typed_by in previous_types_rels:
cur_related_objects = set(is_typed_by.RelatedObjects) - related_objects cur_related_objects = set(is_typed_by.RelatedObjects) - related_objects_set
if cur_related_objects: if cur_related_objects:
is_typed_by.RelatedObjects = list(cur_related_objects) is_typed_by.RelatedObjects = list(cur_related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, **{"element": is_typed_by}) ifcopenshell.api.owner.update_owner_history(self.file, **{"element": is_typed_by})
@@ -244,18 +237,18 @@ class Usecase:
# assign objects to a new type # assign objects to a new type
if types: if types:
types.RelatedObjects = list(set(types.RelatedObjects) | related_objects) types.RelatedObjects = list(set(types.RelatedObjects) | related_objects_set)
ifcopenshell.api.owner.update_owner_history(self.file, **{"element": types}) ifcopenshell.api.owner.update_owner_history(self.file, **{"element": types})
else: else:
types = self.file.create_entity( types = self.file.create_entity(
"IfcRelDefinesByType", "IfcRelDefinesByType",
GlobalId=ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file), OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatedObjects=list(related_objects), RelatedObjects=list(related_objects_set),
RelatingType=relating_type, RelatingType=relating_type,
) )
if self.settings["should_map_representations"]: if should_map_representations:
if getattr(relating_type, "RepresentationMaps", None): if getattr(relating_type, "RepresentationMaps", None):
for related_object in objects_to_change: for related_object in objects_to_change:
ifcopenshell.api.type.map_type_representations( ifcopenshell.api.type.map_type_representations(
@@ -263,11 +256,13 @@ class Usecase:
related_object=related_object, related_object=related_object,
relating_type=relating_type, relating_type=relating_type,
) )
self.map_material_usages(objects_to_change) self.map_material_usages(objects_to_change, relating_type)
return types return types
def map_material_usages(self, related_objects: Iterable[ifcopenshell.entity_instance]) -> None: def map_material_usages(
type_material = ifcopenshell.util.element.get_material(self.settings["relating_type"]) self, related_objects: list[ifcopenshell.entity_instance], relating_type: ifcopenshell.entity_instance
) -> None:
type_material = ifcopenshell.util.element.get_material(relating_type)
if not type_material: if not type_material:
return return
ifc_class = type_material.is_a() ifc_class = type_material.is_a()