mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
black .
This commit is contained in:
@@ -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)
|
||||||
@@ -155,7 +156,7 @@ def register():
|
|||||||
bpy.app.handlers.load_post.append(handler.load_post)
|
bpy.app.handlers.load_post.append(handler.load_post)
|
||||||
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
||||||
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
|
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
|
||||||
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func)
|
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
|||||||
@@ -1655,7 +1655,7 @@ class CutDecorator:
|
|||||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||||
in_local_view = True
|
in_local_view = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# If just entering local view (transition from False to True)
|
# If just entering local view (transition from False to True)
|
||||||
if in_local_view and not self.__class__.was_in_local_view:
|
if in_local_view and not self.__class__.was_in_local_view:
|
||||||
self.__class__.local_view_has_annotation = False
|
self.__class__.local_view_has_annotation = False
|
||||||
@@ -1664,10 +1664,10 @@ class CutDecorator:
|
|||||||
if element and element.is_a("IfcAnnotation"):
|
if element and element.is_a("IfcAnnotation"):
|
||||||
self.__class__.local_view_has_annotation = True
|
self.__class__.local_view_has_annotation = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# Update the state for next time
|
# Update the state for next time
|
||||||
self.__class__.was_in_local_view = in_local_view
|
self.__class__.was_in_local_view = in_local_view
|
||||||
|
|
||||||
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
||||||
if in_local_view and not self.__class__.local_view_has_annotation:
|
if in_local_view and not self.__class__.local_view_has_annotation:
|
||||||
return
|
return
|
||||||
@@ -2066,7 +2066,7 @@ class DecorationsHandler:
|
|||||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||||
in_local_view = True
|
in_local_view = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# If just entering local view (transition from False to True)
|
# If just entering local view (transition from False to True)
|
||||||
if in_local_view and not self.__class__.was_in_local_view:
|
if in_local_view and not self.__class__.was_in_local_view:
|
||||||
self.__class__.local_view_has_annotation = False
|
self.__class__.local_view_has_annotation = False
|
||||||
@@ -2075,10 +2075,10 @@ class DecorationsHandler:
|
|||||||
if element and element.is_a("IfcAnnotation"):
|
if element and element.is_a("IfcAnnotation"):
|
||||||
self.__class__.local_view_has_annotation = True
|
self.__class__.local_view_has_annotation = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# Update the state for next time
|
# Update the state for next time
|
||||||
self.__class__.was_in_local_view = in_local_view
|
self.__class__.was_in_local_view = in_local_view
|
||||||
|
|
||||||
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
||||||
if in_local_view and not self.__class__.local_view_has_annotation:
|
if in_local_view and not self.__class__.local_view_has_annotation:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -344,20 +344,20 @@ 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
|
||||||
|
|
||||||
# Force complete scene update
|
# Force complete scene update
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
context.evaluated_depsgraph_get()
|
context.evaluated_depsgraph_get()
|
||||||
|
|
||||||
underlay_svg = self.generate_underlay(context)
|
underlay_svg = self.generate_underlay(context)
|
||||||
|
|
||||||
with profile("Generate linework"):
|
with profile("Generate linework"):
|
||||||
@@ -3069,9 +3069,9 @@ class AddReference(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
|||||||
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
|
||||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||||
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,66 +4034,66 @@ 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"
|
||||||
bl_description = "Activate the drawing corresponding to the selected annotation"
|
bl_description = "Activate the drawing corresponding to the selected annotation"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
# Check if an annotation object is selected
|
# Check if an annotation object is selected
|
||||||
if not context.selected_objects:
|
if not context.selected_objects:
|
||||||
cls.poll_message_set("No object selected")
|
cls.poll_message_set("No object selected")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
active_obj = context.active_object
|
active_obj = context.active_object
|
||||||
if not active_obj:
|
if not active_obj:
|
||||||
cls.poll_message_set("No active object")
|
cls.poll_message_set("No active object")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(active_obj)
|
element = tool.Ifc.get_entity(active_obj)
|
||||||
if not element:
|
if not element:
|
||||||
cls.poll_message_set("Selected object is not an IFC element")
|
cls.poll_message_set("Selected object is not an IFC element")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if it's an IfcAnnotation with ObjectType = "SECTION" or "ELEVATION"
|
# Check if it's an IfcAnnotation with ObjectType = "SECTION" or "ELEVATION"
|
||||||
if not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
if not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
||||||
cls.poll_message_set("Selected object is not a drawing annotation")
|
cls.poll_message_set("Selected object is not a drawing annotation")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
active_obj = context.active_object
|
active_obj = context.active_object
|
||||||
element = tool.Ifc.get_entity(active_obj)
|
element = tool.Ifc.get_entity(active_obj)
|
||||||
|
|
||||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
||||||
self.report({"ERROR"}, "Selected object is not a drawing annotation")
|
self.report({"ERROR"}, "Selected object is not a drawing annotation")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# Find the drawing/camera element that this annotation references
|
# Find the drawing/camera element that this annotation references
|
||||||
drawing_element = self.find_drawing_from_annotation(element)
|
drawing_element = self.find_drawing_from_annotation(element)
|
||||||
|
|
||||||
if not drawing_element:
|
if not drawing_element:
|
||||||
self.report({"ERROR"}, "Could not find drawing element for this annotation")
|
self.report({"ERROR"}, "Could not find drawing element for this annotation")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# Use the existing ActivateDrawing operator with the drawing element's ID
|
# Use the existing ActivateDrawing operator with the drawing element's ID
|
||||||
bpy.ops.bim.activate_drawing(drawing=drawing_element.id())
|
bpy.ops.bim.activate_drawing(drawing=drawing_element.id())
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def find_drawing_from_annotation(self, annotation_element):
|
def find_drawing_from_annotation(self, annotation_element):
|
||||||
"""Find the drawing/camera element that this annotation references."""
|
"""Find the drawing/camera element that this annotation references."""
|
||||||
ifc = tool.Ifc.get()
|
ifc = tool.Ifc.get()
|
||||||
|
|
||||||
# Check IfcRelAssignsToProduct relationships
|
# Check IfcRelAssignsToProduct relationships
|
||||||
for rel in ifc.get_inverse(annotation_element):
|
for rel in ifc.get_inverse(annotation_element):
|
||||||
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct:
|
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct:
|
||||||
if rel.RelatingProduct.is_a("IfcAnnotation"):
|
if rel.RelatingProduct.is_a("IfcAnnotation"):
|
||||||
# Found the drawing element!
|
# Found the drawing element!
|
||||||
return rel.RelatingProduct
|
return rel.RelatingProduct
|
||||||
|
|
||||||
|
return None
|
||||||
return None
|
|
||||||
|
|||||||
@@ -151,23 +151,23 @@ class FilledOpeningGenerator:
|
|||||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||||
)
|
)
|
||||||
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)
|
||||||
else:
|
else:
|
||||||
# Check for library template before generating from filling
|
# Check for library template before generating from filling
|
||||||
template_rep = self.get_opening_template_from_type(filling)
|
template_rep = self.get_opening_template_from_type(filling)
|
||||||
|
|
||||||
if template_rep:
|
if template_rep:
|
||||||
representation = template_rep
|
representation = template_rep
|
||||||
else:
|
else:
|
||||||
@@ -222,68 +222,68 @@ class FilledOpeningGenerator:
|
|||||||
voided_element = opening.VoidsElements[0].RelatingBuildingElement
|
voided_element = opening.VoidsElements[0].RelatingBuildingElement
|
||||||
|
|
||||||
opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
|
opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
|
||||||
|
|
||||||
# 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)
|
||||||
ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), representation=opening_rep)
|
ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), representation=opening_rep)
|
||||||
|
|
||||||
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
|
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
|
||||||
|
|
||||||
# Priority order for choosing representation:
|
# Priority order for choosing representation:
|
||||||
# 1. Existing occurrence with Tessellation (best quality)
|
# 1. Existing occurrence with Tessellation (best quality)
|
||||||
# 2. Library template with Tessellation
|
# 2. Library template with Tessellation
|
||||||
# 3. Preserved representation from old opening (maintain user's work)
|
# 3. Preserved representation from old opening (maintain user's work)
|
||||||
# 4. Generate from filling (last resort)
|
# 4. Generate from filling (last resort)
|
||||||
|
|
||||||
representation_to_use = None
|
representation_to_use = None
|
||||||
|
|
||||||
if existing_opening_occurrence:
|
if existing_opening_occurrence:
|
||||||
representation = ifcopenshell.util.representation.get_representation(
|
representation = ifcopenshell.util.representation.get_representation(
|
||||||
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:
|
||||||
representation_to_use = preserved_representation
|
representation_to_use = preserved_representation
|
||||||
|
|
||||||
if not representation_to_use:
|
if not representation_to_use:
|
||||||
opening_obj = tool.Ifc.get_object(opening)
|
opening_obj = tool.Ifc.get_object(opening)
|
||||||
if opening_obj:
|
if opening_obj:
|
||||||
@@ -299,7 +299,7 @@ class FilledOpeningGenerator:
|
|||||||
ifcopenshell.api.geometry.assign_representation(
|
ifcopenshell.api.geometry.assign_representation(
|
||||||
tool.Ifc.get(), product=opening, representation=mapped_representation
|
tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||||
)
|
)
|
||||||
|
|
||||||
# update voided object representation...
|
# update voided object representation...
|
||||||
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
|
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
|
||||||
for voided_element in voided_elements:
|
for voided_element in voided_elements:
|
||||||
@@ -314,31 +314,31 @@ 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.
|
||||||
"""
|
"""
|
||||||
element_type = ifcopenshell.util.element.get_type(filling)
|
element_type = ifcopenshell.util.element.get_type(filling)
|
||||||
|
|
||||||
if not element_type:
|
if not element_type:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
desc = element_type.Description
|
desc = element_type.Description
|
||||||
|
|
||||||
if not desc or "||BonsaiOpeningTemplate:" not in desc:
|
if not desc or "||BonsaiOpeningTemplate:" not in desc:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Extract template ID
|
# Extract template ID
|
||||||
marker = desc.split("||BonsaiOpeningTemplate:")[-1]
|
marker = desc.split("||BonsaiOpeningTemplate:")[-1]
|
||||||
template_id = int(marker.split("||")[0])
|
template_id = int(marker.split("||")[0])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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
|
||||||
@@ -729,36 +726,36 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
library_element = library_file.by_guid(element.GlobalId)
|
library_element = library_file.by_guid(element.GlobalId)
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Find occurrences with openings in the library
|
# Find occurrences with openings in the library
|
||||||
library_occurrences = ifcopenshell.util.element.get_types(library_element)
|
library_occurrences = ifcopenshell.util.element.get_types(library_element)
|
||||||
|
|
||||||
for occurrence in library_occurrences:
|
for occurrence in library_occurrences:
|
||||||
if not getattr(occurrence, "FillsVoids", None):
|
if not getattr(occurrence, "FillsVoids", None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
library_opening = occurrence.FillsVoids[0].RelatingOpeningElement
|
library_opening = occurrence.FillsVoids[0].RelatingOpeningElement
|
||||||
library_opening_rep = ifcopenshell.util.representation.get_representation(
|
library_opening_rep = ifcopenshell.util.representation.get_representation(
|
||||||
library_opening, "Model", "Body", "MODEL_VIEW"
|
library_opening, "Model", "Body", "MODEL_VIEW"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not library_opening_rep:
|
if not library_opening_rep:
|
||||||
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
|
||||||
current_desc = element.Description or ""
|
current_desc = element.Description or ""
|
||||||
element.Description = f"{current_desc}||BonsaiOpeningTemplate:{template_rep.id()}"
|
element.Description = f"{current_desc}||BonsaiOpeningTemplate:{template_rep.id()}"
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class TypeData:
|
|||||||
element_type = ifcopenshell.util.element.get_type(element)
|
element_type = ifcopenshell.util.element.get_type(element)
|
||||||
if not element_type:
|
if not element_type:
|
||||||
return results
|
return results
|
||||||
|
|
||||||
data = element_type.get_info()
|
data = element_type.get_info()
|
||||||
if "GlobalId" in data:
|
if "GlobalId" in data:
|
||||||
excluded_keys = ["id", "type"]
|
excluded_keys = ["id", "type"]
|
||||||
|
|||||||
@@ -429,18 +429,18 @@ class EnableEditingTypeAttributes(bpy.types.Operator):
|
|||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if not obj:
|
if not obj:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
element_type = ifcopenshell.util.element.get_type(element)
|
element_type = ifcopenshell.util.element.get_type(element)
|
||||||
if not element_type:
|
if not element_type:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
props = tool.Type.get_object_type_props(obj)
|
props = tool.Type.get_object_type_props(obj)
|
||||||
props.type_attributes.clear()
|
props.type_attributes.clear()
|
||||||
|
|
||||||
bonsai.bim.helper.import_attributes(element_type, props.type_attributes)
|
bonsai.bim.helper.import_attributes(element_type, props.type_attributes)
|
||||||
props.is_editing_type_attributes = True
|
props.is_editing_type_attributes = True
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -456,7 +456,7 @@ class DisableEditingTypeAttributes(bpy.types.Operator):
|
|||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if not obj:
|
if not obj:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
props = tool.Type.get_object_type_props(obj)
|
props = tool.Type.get_object_type_props(obj)
|
||||||
props.type_attributes.clear()
|
props.type_attributes.clear()
|
||||||
props.property_unset("is_editing_type_attributes")
|
props.property_unset("is_editing_type_attributes")
|
||||||
@@ -473,24 +473,24 @@ class EditTypeAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if not obj:
|
if not obj:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
element_type = ifcopenshell.util.element.get_type(element)
|
element_type = ifcopenshell.util.element.get_type(element)
|
||||||
if not element_type:
|
if not element_type:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
props = tool.Type.get_object_type_props(obj)
|
props = tool.Type.get_object_type_props(obj)
|
||||||
attributes = bonsai.bim.helper.export_attributes(props.type_attributes)
|
attributes = bonsai.bim.helper.export_attributes(props.type_attributes)
|
||||||
|
|
||||||
ifcopenshell.api.attribute.edit_attributes(tool.Ifc.get(), product=element_type, attributes=attributes)
|
ifcopenshell.api.attribute.edit_attributes(tool.Ifc.get(), product=element_type, attributes=attributes)
|
||||||
|
|
||||||
type_obj = tool.Ifc.get_object(element_type)
|
type_obj = tool.Ifc.get_object(element_type)
|
||||||
if type_obj:
|
if type_obj:
|
||||||
tool.Root.set_object_name(type_obj, element_type)
|
tool.Root.set_object_name(type_obj, element_type)
|
||||||
|
|
||||||
bpy.ops.bim.disable_editing_type_attributes()
|
bpy.ops.bim.disable_editing_type_attributes()
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -124,27 +124,28 @@ class BIM_PT_type_attributes(Panel):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
if not TypeData.is_loaded:
|
if not TypeData.is_loaded:
|
||||||
TypeData.load()
|
TypeData.load()
|
||||||
|
|
||||||
assert (layout := self.layout)
|
assert (layout := self.layout)
|
||||||
assert (obj := context.active_object)
|
assert (obj := context.active_object)
|
||||||
|
|
||||||
if not TypeData.data.get("relating_type"):
|
if not TypeData.data.get("relating_type"):
|
||||||
layout.label(text="No Relating Type", icon="INFO")
|
layout.label(text="No Relating Type", icon="INFO")
|
||||||
return
|
return
|
||||||
|
|
||||||
props = tool.Type.get_object_type_props(obj)
|
props = tool.Type.get_object_type_props(obj)
|
||||||
|
|
||||||
if props.is_editing_type_attributes:
|
if props.is_editing_type_attributes:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.edit_type_attributes", icon="CHECKMARK", text="Save Attributes")
|
row.operator("bim.edit_type_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||||
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()
|
||||||
row.operator("bim.enable_editing_type_attributes", icon="GREASEPENCIL", text="Edit")
|
row.operator("bim.enable_editing_type_attributes", icon="GREASEPENCIL", text="Edit")
|
||||||
|
|
||||||
for attribute in TypeData.data["relating_type_attributes"]:
|
for attribute in TypeData.data["relating_type_attributes"]:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text=attribute["name"])
|
row.label(text=attribute["name"])
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -91,20 +91,21 @@ class Root(bonsai.core.tool.Root):
|
|||||||
elif dest.is_a("IfcTypeProduct"):
|
elif dest.is_a("IfcTypeProduct"):
|
||||||
if not source.RepresentationMaps:
|
if not source.RepresentationMaps:
|
||||||
return copied_entities
|
return copied_entities
|
||||||
|
|
||||||
# Copy representation maps while preserving mapped representation structures
|
# Copy representation maps while preserving mapped representation structures
|
||||||
new_maps = []
|
new_maps = []
|
||||||
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]
|
||||||
@@ -118,9 +119,9 @@ class Root(bonsai.core.tool.Root):
|
|||||||
exclude_callback=exclude_callback,
|
exclude_callback=exclude_callback,
|
||||||
copied_entities=copied_entities,
|
copied_entities=copied_entities,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_maps.append(new_map)
|
new_maps.append(new_map)
|
||||||
|
|
||||||
dest.RepresentationMaps = new_maps
|
dest.RepresentationMaps = new_maps
|
||||||
return copied_entities
|
return copied_entities
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -928,23 +928,24 @@ 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)"""
|
||||||
bpy.ops.bim.create_project()
|
bpy.ops.bim.create_project()
|
||||||
ifc_path = Path("test/files/temp/test.ifc").absolute()
|
ifc_path = Path("test/files/temp/test.ifc").absolute()
|
||||||
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
|
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
|
||||||
|
|
||||||
# Create a temporary SVG file
|
# Create a temporary SVG file
|
||||||
svg_path = Path("test/files/temp/reference.svg").absolute()
|
svg_path = Path("test/files/temp/reference.svg").absolute()
|
||||||
svg_path.parent.mkdir(parents=True, exist_ok=True)
|
svg_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(svg_path, "w") as f:
|
with open(svg_path, "w") as f:
|
||||||
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
|
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Add single reference
|
# Add single reference
|
||||||
bpy.ops.bim.add_reference(filepath=str(svg_path))
|
bpy.ops.bim.add_reference(filepath=str(svg_path))
|
||||||
|
|
||||||
# Verify reference was added
|
# Verify reference was added
|
||||||
ifc = tool.Ifc.get()
|
ifc = tool.Ifc.get()
|
||||||
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
|
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
|
||||||
@@ -954,24 +955,24 @@ class TestAddReference(NewFile):
|
|||||||
# Cleanup
|
# Cleanup
|
||||||
if svg_path.exists():
|
if svg_path.exists():
|
||||||
svg_path.unlink()
|
svg_path.unlink()
|
||||||
|
|
||||||
def test_add_multiple_references(self):
|
def test_add_multiple_references(self):
|
||||||
"""Test adding multiple reference files at once"""
|
"""Test adding multiple reference files at once"""
|
||||||
bpy.ops.bim.create_project()
|
bpy.ops.bim.create_project()
|
||||||
ifc_path = Path("test/files/temp/test.ifc").absolute()
|
ifc_path = Path("test/files/temp/test.ifc").absolute()
|
||||||
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
|
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
|
||||||
|
|
||||||
# Create temporary SVG files
|
# Create temporary SVG files
|
||||||
temp_dir = Path("test/files/temp").absolute()
|
temp_dir = Path("test/files/temp").absolute()
|
||||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
svg_files = []
|
svg_files = []
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
svg_path = temp_dir / f"reference_{i}.svg"
|
svg_path = temp_dir / f"reference_{i}.svg"
|
||||||
with open(svg_path, "w") as f:
|
with open(svg_path, "w") as f:
|
||||||
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
|
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
|
||||||
svg_files.append(svg_path)
|
svg_files.append(svg_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Test by directly calling core.add_document multiple times
|
# Test by directly calling core.add_document multiple times
|
||||||
# (simulating what the operator does with multiple files)
|
# (simulating what the operator does with multiple files)
|
||||||
@@ -979,12 +980,13 @@ 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
|
||||||
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
|
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
|
||||||
assert len(references) == 3
|
assert len(references) == 3
|
||||||
|
|
||||||
reference_names = {ref.Name for ref in references}
|
reference_names = {ref.Name for ref in references}
|
||||||
expected_names = {f"reference_{i}" for i in range(3)}
|
expected_names = {f"reference_{i}" for i in range(3)}
|
||||||
assert reference_names == expected_names
|
assert reference_names == expected_names
|
||||||
|
|||||||
Reference in New Issue
Block a user