This commit is contained in:
Andrej730
2025-12-18 18:47:07 +05:00
parent 4d52480cbe
commit 2581c36a5f
12 changed files with 134 additions and 129 deletions
@@ -143,6 +143,7 @@ def menu_func(self, context):
if element and element.is_a("IfcAnnotation") and element.ObjectType in ["SECTION", "ELEVATION"]: if element and element.is_a("IfcAnnotation") and element.ObjectType in ["SECTION", "ELEVATION"]:
self.layout.operator("bim.activate_drawing_by_annotation", text="Go to Drawing") self.layout.operator("bim.activate_drawing_by_annotation", text="Go to Drawing")
def register(): def register():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False) bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
@@ -344,13 +344,13 @@ class CreateDrawing(bpy.types.Operator):
# Clear any local camera setup and force viewport to use scene camera # Clear any local camera setup and force viewport to use scene camera
for area in context.screen.areas: for area in context.screen.areas:
if area.type == 'VIEW_3D': if area.type == "VIEW_3D":
for space in area.spaces: for space in area.spaces:
if space.type == 'VIEW_3D': if space.type == "VIEW_3D":
# Clear local camera to ensure we use scene.camera # Clear local camera to ensure we use scene.camera
space.use_local_camera = False space.use_local_camera = False
space.camera = context.scene.camera space.camera = context.scene.camera
space.region_3d.view_perspective = 'CAMERA' space.region_3d.view_perspective = "CAMERA"
print(f"Set viewport camera to: {context.scene.camera.name}") print(f"Set viewport camera to: {context.scene.camera.name}")
break break
@@ -3071,7 +3071,7 @@ class AddReference(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
filename_ext = ".svg" filename_ext = ".svg"
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement) files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)
directory: bpy.props.StringProperty(subtype='DIR_PATH') directory: bpy.props.StringProperty(subtype="DIR_PATH")
def _execute(self, context): def _execute(self, context):
# Handle both single and multiple file selection # Handle both single and multiple file selection
@@ -4034,6 +4034,7 @@ class ExcludeAnnotation(bpy.types.Operator, tool.Ifc.Operator):
tool.Drawing.exclude_annotation_from_drawing(referenced_element, drawing) tool.Drawing.exclude_annotation_from_drawing(referenced_element, drawing)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing) core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
class ActivateDrawingByAnnotation(bpy.types.Operator, tool.Ifc.Operator): class ActivateDrawingByAnnotation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.activate_drawing_by_annotation" bl_idname = "bim.activate_drawing_by_annotation"
bl_label = "Activate Drawing" bl_label = "Activate Drawing"
@@ -4095,5 +4096,4 @@ class ActivateDrawingByAnnotation(bpy.types.Operator, tool.Ifc.Operator):
# Found the drawing element! # Found the drawing element!
return rel.RelatingProduct return rel.RelatingProduct
return None return None
+26 -26
View File
@@ -153,14 +153,14 @@ class FilledOpeningGenerator:
assert representation assert representation
# Check if mapped representation - preserve it # Check if mapped representation - preserve it
if (representation.RepresentationType == 'MappedRepresentation' and if (
len(representation.Items) == 1 and representation.RepresentationType == "MappedRepresentation"
representation.Items[0].is_a("IfcMappedItem")): and len(representation.Items) == 1
and representation.Items[0].is_a("IfcMappedItem")
):
source_rep = representation.Items[0].MappingSource.MappedRepresentation source_rep = representation.Items[0].MappingSource.MappedRepresentation
representation = ifcopenshell.util.element.copy_deep( representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"]
source_rep,
exclude=["IfcGeometricRepresentationContext"]
) )
else: else:
representation = ifcopenshell.util.representation.resolve_representation(representation) representation = ifcopenshell.util.representation.resolve_representation(representation)
@@ -226,21 +226,21 @@ class FilledOpeningGenerator:
# ALWAYS preserve the existing opening representation (Tessellation, SweptSolid, etc.) # ALWAYS preserve the existing opening representation (Tessellation, SweptSolid, etc.)
preserved_representation = None preserved_representation = None
if opening_rep: if opening_rep:
if (opening_rep.RepresentationType == 'MappedRepresentation' and if (
len(opening_rep.Items) == 1 and opening_rep.RepresentationType == "MappedRepresentation"
opening_rep.Items[0].is_a("IfcMappedItem")): and len(opening_rep.Items) == 1
and opening_rep.Items[0].is_a("IfcMappedItem")
):
# For mapped representations, copy the underlying representation # For mapped representations, copy the underlying representation
preserved_representation = ifcopenshell.util.element.copy_deep( preserved_representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), tool.Ifc.get(),
opening_rep.Items[0].MappingSource.MappedRepresentation, opening_rep.Items[0].MappingSource.MappedRepresentation,
exclude=["IfcGeometricRepresentationContext"] exclude=["IfcGeometricRepresentationContext"],
) )
else: else:
# For direct representations (non-mapped), copy them too # For direct representations (non-mapped), copy them too
preserved_representation = ifcopenshell.util.element.copy_deep( preserved_representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), tool.Ifc.get(), opening_rep, exclude=["IfcGeometricRepresentationContext"]
opening_rep,
exclude=["IfcGeometricRepresentationContext"]
) )
ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep) ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep)
@@ -261,24 +261,24 @@ class FilledOpeningGenerator:
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
) )
if (representation and if (
representation.RepresentationType == 'MappedRepresentation' and representation
len(representation.Items) == 1 and and representation.RepresentationType == "MappedRepresentation"
representation.Items[0].is_a("IfcMappedItem")): and len(representation.Items) == 1
and representation.Items[0].is_a("IfcMappedItem")
):
source_rep = representation.Items[0].MappingSource.MappedRepresentation source_rep = representation.Items[0].MappingSource.MappedRepresentation
# Prefer Tessellation from existing occurrence over preserved representation # Prefer Tessellation from existing occurrence over preserved representation
if source_rep.RepresentationType == 'Tessellation': if source_rep.RepresentationType == "Tessellation":
representation_to_use = ifcopenshell.util.element.copy_deep( representation_to_use = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"]
source_rep,
exclude=["IfcGeometricRepresentationContext"]
) )
else: else:
representation_to_use = ifcopenshell.util.representation.resolve_representation(representation) representation_to_use = ifcopenshell.util.representation.resolve_representation(representation)
if not representation_to_use: if not representation_to_use:
template_rep = self.get_opening_template_from_type(filling) template_rep = self.get_opening_template_from_type(filling)
if template_rep and template_rep.RepresentationType == 'Tessellation': if template_rep and template_rep.RepresentationType == "Tessellation":
representation_to_use = template_rep representation_to_use = template_rep
if not representation_to_use and preserved_representation: if not representation_to_use and preserved_representation:
@@ -314,7 +314,9 @@ class FilledOpeningGenerator:
representation=representation, representation=representation,
) )
def get_opening_template_from_type(self, filling: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: def get_opening_template_from_type(
self, filling: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
""" """
Check if the filling's type has a stored opening template from library import. Check if the filling's type has a stored opening template from library import.
""" """
@@ -336,9 +338,7 @@ class FilledOpeningGenerator:
template_rep = tool.Ifc.get().by_id(template_id) template_rep = tool.Ifc.get().by_id(template_id)
# Make a copy so we don't reuse the same representation instance # Make a copy so we don't reuse the same representation instance
copied = ifcopenshell.util.element.copy_deep( copied = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), tool.Ifc.get(), template_rep, exclude=["IfcGeometricRepresentationContext"]
template_rep,
exclude=["IfcGeometricRepresentationContext"]
) )
return copied return copied
except: except:
@@ -715,11 +715,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()): if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()):
ifc_importer.create_style(element) ifc_importer.create_style(element)
def store_opening_template_from_library( def store_opening_template_from_library(
self, self, element: ifcopenshell.entity_instance, library_file: ifcopenshell.file
element: ifcopenshell.entity_instance,
library_file: ifcopenshell.file
) -> None: ) -> None:
""" """
Find an opening representation in the library and copy it to the current file Find an opening representation in the library and copy it to the current file
@@ -746,17 +743,17 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
continue continue
# Check if mapped representation # Check if mapped representation
if (library_opening_rep.RepresentationType == 'MappedRepresentation' and if (
len(library_opening_rep.Items) == 1 and library_opening_rep.RepresentationType == "MappedRepresentation"
library_opening_rep.Items[0].is_a("IfcMappedItem")): and len(library_opening_rep.Items) == 1
and library_opening_rep.Items[0].is_a("IfcMappedItem")
):
mapped_rep = library_opening_rep.Items[0].MappingSource.MappedRepresentation mapped_rep = library_opening_rep.Items[0].MappingSource.MappedRepresentation
# Store ALL representation types (Tessellation, SweptSolid, etc.) # Store ALL representation types (Tessellation, SweptSolid, etc.)
template_rep = ifcopenshell.util.element.copy_deep( template_rep = ifcopenshell.util.element.copy_deep(
self.file, self.file, mapped_rep, exclude=["IfcGeometricRepresentationContext"]
mapped_rep,
exclude=["IfcGeometricRepresentationContext"]
) )
# Store reference in type's Description # Store reference in type's Description
+1
View File
@@ -140,6 +140,7 @@ class BIM_PT_type_attributes(Panel):
row.operator("bim.disable_editing_type_attributes", icon="CANCEL", text="") row.operator("bim.disable_editing_type_attributes", icon="CANCEL", text="")
import bonsai.bim.helper import bonsai.bim.helper
bonsai.bim.helper.draw_attributes(props.type_attributes, layout) bonsai.bim.helper.draw_attributes(props.type_attributes, layout)
else: else:
row = layout.row() row = layout.row()
+6 -2
View File
@@ -28,9 +28,13 @@ if TYPE_CHECKING:
def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None: def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None:
if unit.is_scene_unit_metric(): if unit.is_scene_unit_metric():
lengthunit = ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT")) lengthunit = ifc.run(
"unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT")
)
areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=unit.get_scene_unit_si_prefix("AREAUNIT")) areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=unit.get_scene_unit_si_prefix("AREAUNIT"))
volumeunit = ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT")) volumeunit = ifc.run(
"unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT")
)
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree") planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
units = [lengthunit, areaunit, volumeunit, planeangleunit] units = [lengthunit, areaunit, volumeunit, planeangleunit]
+5 -4
View File
@@ -97,14 +97,15 @@ class Root(bonsai.core.tool.Root):
for i, rep_map in enumerate(source.RepresentationMaps): for i, rep_map in enumerate(source.RepresentationMaps):
source_rep = rep_map.MappedRepresentation source_rep = rep_map.MappedRepresentation
# Copy the map itself # Copy the map itself
new_map = ifcopenshell.util.element.copy(tool.Ifc.get(), rep_map) new_map = ifcopenshell.util.element.copy(tool.Ifc.get(), rep_map)
# Handle the mapped representation - preserve mapping structure if present # Handle the mapped representation - preserve mapping structure if present
if (source_rep.RepresentationType == 'MappedRepresentation' and if (
len(source_rep.Items) == 1 and source_rep.RepresentationType == "MappedRepresentation"
source_rep.Items[0].is_a("IfcMappedItem")): and len(source_rep.Items) == 1
and source_rep.Items[0].is_a("IfcMappedItem")
):
# This is a mapped representation - preserve the structure # This is a mapped representation - preserve the structure
new_rep = ifcopenshell.util.element.copy(tool.Ifc.get(), source_rep) new_rep = ifcopenshell.util.element.copy(tool.Ifc.get(), source_rep)
new_rep.Items = [ifcopenshell.util.element.copy(tool.Ifc.get(), item) for item in source_rep.Items] new_rep.Items = [ifcopenshell.util.element.copy(tool.Ifc.get(), item) for item in source_rep.Items]
-1
View File
@@ -93,7 +93,6 @@ class TestAssignSceneUnits:
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called() ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called()
subject.assign_scene_units(ifc, unit) subject.assign_scene_units(ifc, unit)
def test_creating_metric_units_with_conversion_based_mass_and_time(self, ifc, unit): def test_creating_metric_units_with_conversion_based_mass_and_time(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(True) unit.is_scene_unit_metric().should_be_called().will_return(True)
unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("MILLI") unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("MILLI")
+2
View File
@@ -928,6 +928,7 @@ class TestAddReferenceImage(NewFile):
uv_node = material_nodes["Texture Coordinate"] uv_node = material_nodes["Texture Coordinate"]
assert len(uv_node.outputs["Generated"].links[:]) == 1 assert len(uv_node.outputs["Generated"].links[:]) == 1
class TestAddReference(NewFile): class TestAddReference(NewFile):
def test_add_single_reference(self): def test_add_single_reference(self):
"""Test adding a single reference file (backward compatibility)""" """Test adding a single reference file (backward compatibility)"""
@@ -979,6 +980,7 @@ class TestAddReference(NewFile):
for svg_file in svg_files: for svg_file in svg_files:
uri = tool.Ifc.get_uri(str(svg_file), use_relative_path=True) uri = tool.Ifc.get_uri(str(svg_file), use_relative_path=True)
from bonsai.bim import core from bonsai.bim import core
core.drawing.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=uri) core.drawing.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=uri)
# Verify all references were added # Verify all references were added