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)
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
edge2 = p3 - p1
normal = edge1.cross(edge2)
assert isinstance(normal, Vector)
z_axis = normal.normalized()
x_axis = p2 - p1
x_axis.normalize()
y_axis = z_axis.cross(x_axis)
assert isinstance(y_axis, Vector)
mat = Matrix()
mat.col[0][:3] = x_axis
@@ -1059,7 +1062,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
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
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
import bonsai.tool as tool
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):
@@ -695,6 +695,9 @@ class ExportCostSchedules(bpy.types.Operator, ExportHelper):
default=True,
)
if TYPE_CHECKING:
format: Literal["CSV", "XLSX", "ODS"]
@property
def filename_ext(self) -> str:
return f".{self.format.lower()}"
+20 -8
View File
@@ -24,11 +24,13 @@ import ifcopenshell.util.representation
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.api
import ifcopenshell.api.type
import bonsai.bim.helper
import bonsai.tool as tool
import bonsai.core.geometry
import bonsai.core.type as core
import bonsai.core.root
from typing import TYPE_CHECKING
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()
related_object: bpy.props.StringProperty()
if TYPE_CHECKING:
relating_type: int
related_object: str
def _execute(self, context):
relating_type = tool.Ifc.get().by_id(
self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
)
related_objects = (
[bpy.data.objects.get(self.related_object)]
if self.related_object
else context.selected_objects or [context.active_object]
)
if self.related_object:
related_objects = [bpy.data.objects[self.related_object]]
else:
related_objects = tool.Blender.get_selected_objects()
model_props = tool.Model.get_model_props()
for obj in related_objects:
element = tool.Ifc.get_entity(obj)
@@ -63,17 +68,24 @@ class UnassignType(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty()
if TYPE_CHECKING:
related_object: str
def _execute(self, context):
def exclude_callback(attribute):
return attribute.is_a("IfcProfileDef") and attribute.ProfileName
self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
for obj in objs:
if self.related_object:
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)
if not element or not element.is_a("IfcObject"):
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:
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(
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]:
cost.play_sound()
return cost.export_cost_schedules(filepath, format, cost_schedule)
+7 -1
View File
@@ -29,6 +29,7 @@ import bonsai.bim.helper
import json
from pathlib import Path
from typing import Optional, Any, Generator, Union, Literal, TYPE_CHECKING
from typing_extensions import assert_never
if TYPE_CHECKING:
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
@@ -652,7 +653,10 @@ class Cost(bonsai.core.tool.Cost):
@classmethod
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]:
import subprocess
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.write()
else:
assert_never(format)
try:
if path:
if sys.platform == "win32":
@@ -92,18 +92,14 @@ def assign_type(
ambiguous, unknown or are so bespoke as to have no logical type.
:param related_objects: The IfcElement occurrences.
:type related_objects: list[ifcopenshell.entity_instance]
:param relating_type: The IfcElementType type.
:type relating_type: ifcopenshell.entity_instance
:param should_map_representations: If a type has a representation map,
IFC requires all occurrences to map those representations. Some IFC
vendors might disobey this, or you might want to handle it
yourusecase. In this scenario, you may set this to False.
This also enabled adding material usages mapping.
:type should_map_representations: bool
:return: The IfcRelDefinesByType relationship
or `None` if `related_objects` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -177,29 +173,26 @@ def assign_type(
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"related_objects": related_objects,
"relating_type": relating_type,
"should_map_representations": should_map_representations,
}
return usecase.execute()
return usecase.execute(related_objects, relating_type, should_map_representations)
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
if not self.settings["related_objects"]:
def execute(
self,
related_objects: list[ifcopenshell.entity_instance],
relating_type: ifcopenshell.entity_instance,
should_map_representations: bool,
):
if not related_objects:
return
related_objects = set(self.settings["related_objects"])
relating_type = self.settings["relating_type"]
related_objects_set = set(related_objects)
ifc2x3 = self.file.schema == "IFC2X3"
related_objects = set(self.settings["related_objects"])
relating_type = self.settings["relating_type"]
related_objects_set = set(related_objects)
if ifc2x3:
types = next(iter(relating_type.ObjectTypeOf), None)
else:
@@ -210,20 +203,20 @@ class Usecase:
objects_with_types: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for object in related_objects:
for obj in related_objects_set:
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:
object_rel = next(iter(object.IsTypedBy), None)
object_rel = next(iter(obj.IsTypedBy), None)
if object_rel is None:
objects_without_types.append(object)
objects_without_types.append(obj)
continue
# either rel doesn't exist or product is part of different rel
if object_rel != types:
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
# nothing to change
@@ -232,7 +225,7 @@ class Usecase:
# unassign from previous types
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:
is_typed_by.RelatedObjects = list(cur_related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, **{"element": is_typed_by})
@@ -244,18 +237,18 @@ class Usecase:
# assign objects to a new type
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})
else:
types = self.file.create_entity(
"IfcRelDefinesByType",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
RelatedObjects=list(related_objects),
RelatedObjects=list(related_objects_set),
RelatingType=relating_type,
)
if self.settings["should_map_representations"]:
if should_map_representations:
if getattr(relating_type, "RepresentationMaps", None):
for related_object in objects_to_change:
ifcopenshell.api.type.map_type_representations(
@@ -263,11 +256,13 @@ class Usecase:
related_object=related_object,
relating_type=relating_type,
)
self.map_material_usages(objects_to_change)
self.map_material_usages(objects_to_change, relating_type)
return types
def map_material_usages(self, related_objects: Iterable[ifcopenshell.entity_instance]) -> None:
type_material = ifcopenshell.util.element.get_material(self.settings["relating_type"])
def map_material_usages(
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:
return
ifc_class = type_material.is_a()