mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Integrate manual drawing references into annotation tool
Adds MANUAL_DRAWING_REFERENCE to the annotation type dropdown. Selecting it shows a dialog to choose elevation or section and optionally assign a target drawing before placement. Tags are protected from regeneration via EPset_Annotation.IsManualDrawingReference. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -30,7 +30,6 @@ classes = (
|
||||
operator.ActivateModel,
|
||||
operator.AddAnnotation,
|
||||
operator.AddAnnotationType,
|
||||
operator.AddManualDrawingReference,
|
||||
operator.AssignManualDrawingReference,
|
||||
operator.AddDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
|
||||
@@ -1727,11 +1727,29 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return drawing_path
|
||||
|
||||
|
||||
def _get_drawing_enum_items(self, context):
|
||||
items = [("0", "None", "Clear the drawing assignment")]
|
||||
if ifc := tool.Ifc.get():
|
||||
for d in ifc.by_type("IfcAnnotation"):
|
||||
if d.ObjectType == "DRAWING":
|
||||
items.append((str(d.id()), d.Name or f"Drawing {d.id()}", ""))
|
||||
return items
|
||||
|
||||
|
||||
class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_annotation"
|
||||
bl_label = "Add Annotation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
description: bpy.props.StringProperty()
|
||||
annotation_subtype: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
items=[
|
||||
("ELEVATION", "Elevation", "Place a manual elevation tag"),
|
||||
("SECTION", "Section", "Place a manual section tag"),
|
||||
],
|
||||
default="ELEVATION",
|
||||
)
|
||||
drawing_id: bpy.props.EnumProperty(name="Drawing", items=_get_drawing_enum_items)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -1741,6 +1759,16 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def description(cls, context, operator):
|
||||
return operator.description or ""
|
||||
|
||||
def invoke(self, context, event):
|
||||
if tool.Drawing.get_annotation_props().object_type == "MANUAL_DRAWING_REFERENCE":
|
||||
self.drawing_id = "0"
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, "annotation_subtype", expand=True)
|
||||
self.layout.prop(self, "drawing_id")
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Drawing.get_annotation_props()
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
@@ -1748,69 +1776,25 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"WARNING"}, "No active drawing.")
|
||||
return
|
||||
|
||||
object_type = self.annotation_subtype if props.object_type == "MANUAL_DRAWING_REFERENCE" else props.object_type
|
||||
obj = core.add_annotation(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Drawing,
|
||||
drawing=drawing,
|
||||
object_type=props.object_type,
|
||||
object_type=object_type,
|
||||
relating_type=tool.Ifc.get().by_id(int(props.relating_type_id)) if props.relating_type_id != "0" else None,
|
||||
enable_editing=True,
|
||||
)
|
||||
if props.object_type == "IMAGE":
|
||||
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
|
||||
|
||||
|
||||
class AddManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_manual_drawing_reference"
|
||||
bl_label = "Add Manual Drawing Reference"
|
||||
bl_description = "Place a manual elevation or section tag that will not be moved or deleted during drawing regeneration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
annotation_type: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
items=[
|
||||
("ELEVATION", "Elevation", "Place a manual elevation tag"),
|
||||
("SECTION", "Section", "Place a manual section tag"),
|
||||
],
|
||||
default="ELEVATION",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
return False
|
||||
if not context.scene.camera:
|
||||
cls.poll_message_set("No active drawing.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, "annotation_type")
|
||||
|
||||
def _execute(self, context):
|
||||
dprops = tool.Drawing.get_document_props()
|
||||
if not (drawing := dprops.get_active_drawing()):
|
||||
self.report({"WARNING"}, "No active drawing.")
|
||||
return
|
||||
core.add_manual_drawing_reference(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Drawing,
|
||||
drawing=drawing,
|
||||
annotation_type=self.annotation_type,
|
||||
)
|
||||
|
||||
|
||||
def _get_drawing_enum_items(self, context):
|
||||
items = [("0", "None", "Clear the drawing assignment")]
|
||||
if ifc := tool.Ifc.get():
|
||||
for d in ifc.by_type("IfcAnnotation"):
|
||||
if d.ObjectType == "DRAWING":
|
||||
items.append((str(d.id()), d.Name or f"Drawing {d.id()}", ""))
|
||||
return items
|
||||
if object_type in ("ELEVATION", "SECTION"):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
tool.Drawing.set_manual_drawing_reference(element)
|
||||
if self.drawing_id != "0":
|
||||
core.assign_manual_drawing_reference(
|
||||
tool.Ifc, tool.Drawing, element=element, drawing=tool.Ifc.get().by_id(int(self.drawing_id))
|
||||
)
|
||||
|
||||
|
||||
class AssignManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -535,6 +535,16 @@ class BIM_PT_product_assignments(Panel):
|
||||
|
||||
assert self.layout
|
||||
assert (obj := context.active_object)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and tool.Drawing.is_manual_drawing_reference(element):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(
|
||||
text=ProductAssignmentsData.data["relating_product"] or "No Drawing Assigned", icon="IMAGE_DATA"
|
||||
)
|
||||
row.operator("bim.assign_manual_drawing_reference", icon="GREASEPENCIL", text="")
|
||||
return
|
||||
|
||||
props = tool.Drawing.get_object_assigned_product_props(obj)
|
||||
|
||||
if props.is_editing_product:
|
||||
|
||||
@@ -332,7 +332,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def hotkey_S_A(self):
|
||||
if bpy.ops.bim.add_annotation.poll():
|
||||
bpy.ops.bim.add_annotation()
|
||||
bpy.ops.bim.add_annotation("INVOKE_DEFAULT")
|
||||
|
||||
def hotkey_S_E(self):
|
||||
if not bpy.context.active_object:
|
||||
|
||||
@@ -518,28 +518,6 @@ def assign_manual_drawing_reference(
|
||||
ifc.run("drawing.assign_product", relating_product=drawing, related_object=element)
|
||||
|
||||
|
||||
def add_manual_drawing_reference(
|
||||
ifc: type[tool.Ifc],
|
||||
collector: type[tool.Collector],
|
||||
drawing_tool: type[tool.Drawing],
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
annotation_type: str,
|
||||
) -> bpy.types.Object:
|
||||
if annotation_type == "ELEVATION":
|
||||
element = drawing_tool.create_manual_elevation_reference(drawing)
|
||||
else: # SECTION
|
||||
target_view = drawing_tool.get_drawing_target_view(drawing)
|
||||
context = drawing_tool.get_annotation_context(target_view)
|
||||
if not context:
|
||||
context = drawing_tool.create_annotation_context(target_view, annotation_type)
|
||||
element = drawing_tool.create_manual_section_reference(drawing, context)
|
||||
drawing_tool.set_manual_drawing_reference(element)
|
||||
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), products=[element])
|
||||
obj = ifc.get_object(element)
|
||||
collector.assign(obj)
|
||||
return obj
|
||||
|
||||
|
||||
def build_schedule(drawing: type[tool.Drawing], schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.create_svg_schedule(schedule)
|
||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
||||
|
||||
@@ -111,6 +111,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
"FILL_AREA": AnnotationObjectType("Fill Area", "", "NODE_TEXTURE", "mesh"),
|
||||
"FALL": AnnotationObjectType("Fall", "", "SORT_ASC", "curve"),
|
||||
"IMAGE": AnnotationObjectType("Image", "Add reference image attached to the drawing", "TEXTURE", "mesh"),
|
||||
"MANUAL_DRAWING_REFERENCE": AnnotationObjectType("Manual Drawing Reference", "Add manual elevation or section reference tag that will not be moved or deleted during drawing regeneration", "EMPTY_ARROWS", "empty"),
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
@@ -177,6 +178,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def get_annotation_data_type(cls, object_type: str) -> ANNOTATION_DATA_TYPE:
|
||||
if object_type == "ELEVATION":
|
||||
return "empty"
|
||||
if object_type == "SECTION":
|
||||
return "mesh"
|
||||
return cls.ANNOTATION_TYPES_DATA[object_type].data_type
|
||||
|
||||
@classmethod
|
||||
@@ -207,6 +212,13 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
co_end = co1 + vec * scaled_length
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj, co_end, co1)
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(math.radians(-90), 4, "Z")
|
||||
elif object_type == "ELEVATION":
|
||||
obj.matrix_world = Matrix.Translation(bpy.context.scene.cursor.location.copy()) @ Matrix.Rotation(
|
||||
math.radians(90), 4, "X"
|
||||
)
|
||||
elif object_type == "SECTION":
|
||||
obj.matrix_world = Matrix.Translation(bpy.context.scene.cursor.location.copy())
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj)
|
||||
elif object_type != "TEXT":
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj)
|
||||
|
||||
@@ -1530,6 +1542,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
elements.append(element)
|
||||
return elements
|
||||
|
||||
@classmethod
|
||||
def is_manual_drawing_reference(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
return bool(ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "IsManualDrawingReference"))
|
||||
|
||||
@classmethod
|
||||
def is_auto_annotation(cls, element: ifcopenshell.entity_instance):
|
||||
if not (element.is_a("IfcAnnotation") and element.ObjectType in ("GRID", "SECTION", "ELEVATION", "SECTION_LEVEL")):
|
||||
@@ -1864,40 +1880,6 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
element.Name = elevation.Name or "Unnamed"
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def create_manual_elevation_reference(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
cursor_location = bpy.context.scene.cursor.location.copy()
|
||||
obj = bpy.data.objects.new("Unnamed", None)
|
||||
obj.empty_display_size = 0.1
|
||||
obj.matrix_world = Matrix.Translation(cursor_location) @ Matrix.Rotation(math.radians(90), 4, "X")
|
||||
element = cls.run_root_assign_class(
|
||||
obj=obj, ifc_class="IfcAnnotation", predefined_type="ELEVATION", should_add_representation=False
|
||||
)
|
||||
element.Name = "Unnamed"
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def create_manual_section_reference(
|
||||
cls, drawing: ifcopenshell.entity_instance, context: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
cursor_location = bpy.context.scene.cursor.location.copy()
|
||||
mesh = bpy.data.meshes.new("Mesh")
|
||||
obj = bpy.data.objects.new("Unnamed", mesh)
|
||||
obj.matrix_world = Matrix.Translation(cursor_location)
|
||||
element = cls.run_root_assign_class(
|
||||
obj=obj, ifc_class="IfcAnnotation", predefined_type="SECTION", should_add_representation=False
|
||||
)
|
||||
element.Name = "Unnamed"
|
||||
builder = ShapeBuilder(tool.Ifc.get())
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
p1 = cursor_location + Vector((-0.5, 0, 0))
|
||||
p2 = cursor_location + Vector((0.5, 0, 0))
|
||||
points = [p1 / unit_scale, p2 / unit_scale]
|
||||
representation = builder.get_representation(context, [builder.polyline(points)])
|
||||
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||
bonsai.core.geometry.switch_representation(tool.Ifc, tool.Geometry, obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def set_manual_drawing_reference(cls, element: ifcopenshell.entity_instance) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
Reference in New Issue
Block a user