mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 22:47:53 +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.ActivateModel,
|
||||||
operator.AddAnnotation,
|
operator.AddAnnotation,
|
||||||
operator.AddAnnotationType,
|
operator.AddAnnotationType,
|
||||||
operator.AddManualDrawingReference,
|
|
||||||
operator.AssignManualDrawingReference,
|
operator.AssignManualDrawingReference,
|
||||||
operator.AddDrawing,
|
operator.AddDrawing,
|
||||||
operator.AddDrawingStyle,
|
operator.AddDrawingStyle,
|
||||||
|
|||||||
@@ -1727,11 +1727,29 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
return drawing_path
|
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):
|
class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.add_annotation"
|
bl_idname = "bim.add_annotation"
|
||||||
bl_label = "Add Annotation"
|
bl_label = "Add Annotation"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
description: bpy.props.StringProperty()
|
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
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -1741,6 +1759,16 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def description(cls, context, operator):
|
def description(cls, context, operator):
|
||||||
return operator.description or ""
|
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):
|
def _execute(self, context):
|
||||||
props = tool.Drawing.get_annotation_props()
|
props = tool.Drawing.get_annotation_props()
|
||||||
dprops = tool.Drawing.get_document_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.")
|
self.report({"WARNING"}, "No active drawing.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
object_type = self.annotation_subtype if props.object_type == "MANUAL_DRAWING_REFERENCE" else props.object_type
|
||||||
obj = core.add_annotation(
|
obj = core.add_annotation(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
tool.Collector,
|
tool.Collector,
|
||||||
tool.Drawing,
|
tool.Drawing,
|
||||||
drawing=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,
|
relating_type=tool.Ifc.get().by_id(int(props.relating_type_id)) if props.relating_type_id != "0" else None,
|
||||||
enable_editing=True,
|
enable_editing=True,
|
||||||
)
|
)
|
||||||
if props.object_type == "IMAGE":
|
if props.object_type == "IMAGE":
|
||||||
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
|
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
|
||||||
|
if object_type in ("ELEVATION", "SECTION"):
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
class AddManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
tool.Drawing.set_manual_drawing_reference(element)
|
||||||
bl_idname = "bim.add_manual_drawing_reference"
|
if self.drawing_id != "0":
|
||||||
bl_label = "Add Manual Drawing Reference"
|
core.assign_manual_drawing_reference(
|
||||||
bl_description = "Place a manual elevation or section tag that will not be moved or deleted during drawing regeneration"
|
tool.Ifc, tool.Drawing, element=element, drawing=tool.Ifc.get().by_id(int(self.drawing_id))
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class AssignManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
class AssignManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
|||||||
@@ -535,6 +535,16 @@ class BIM_PT_product_assignments(Panel):
|
|||||||
|
|
||||||
assert self.layout
|
assert self.layout
|
||||||
assert (obj := context.active_object)
|
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)
|
props = tool.Drawing.get_object_assigned_product_props(obj)
|
||||||
|
|
||||||
if props.is_editing_product:
|
if props.is_editing_product:
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def hotkey_S_A(self):
|
def hotkey_S_A(self):
|
||||||
if bpy.ops.bim.add_annotation.poll():
|
if bpy.ops.bim.add_annotation.poll():
|
||||||
bpy.ops.bim.add_annotation()
|
bpy.ops.bim.add_annotation("INVOKE_DEFAULT")
|
||||||
|
|
||||||
def hotkey_S_E(self):
|
def hotkey_S_E(self):
|
||||||
if not bpy.context.active_object:
|
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)
|
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:
|
def build_schedule(drawing: type[tool.Drawing], schedule: ifcopenshell.entity_instance) -> None:
|
||||||
drawing.create_svg_schedule(schedule)
|
drawing.create_svg_schedule(schedule)
|
||||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
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"),
|
"FILL_AREA": AnnotationObjectType("Fill Area", "", "NODE_TEXTURE", "mesh"),
|
||||||
"FALL": AnnotationObjectType("Fall", "", "SORT_ASC", "curve"),
|
"FALL": AnnotationObjectType("Fall", "", "SORT_ASC", "curve"),
|
||||||
"IMAGE": AnnotationObjectType("Image", "Add reference image attached to the drawing", "TEXTURE", "mesh"),
|
"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
|
# fmt: on
|
||||||
|
|
||||||
@@ -177,6 +178,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_annotation_data_type(cls, object_type: str) -> ANNOTATION_DATA_TYPE:
|
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
|
return cls.ANNOTATION_TYPES_DATA[object_type].data_type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -207,6 +212,13 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
co_end = co1 + vec * scaled_length
|
co_end = co1 + vec * scaled_length
|
||||||
obj = annotation.Annotator.add_line_to_annotation(obj, co_end, co1)
|
obj = annotation.Annotator.add_line_to_annotation(obj, co_end, co1)
|
||||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(math.radians(-90), 4, "Z")
|
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":
|
elif object_type != "TEXT":
|
||||||
obj = annotation.Annotator.add_line_to_annotation(obj)
|
obj = annotation.Annotator.add_line_to_annotation(obj)
|
||||||
|
|
||||||
@@ -1530,6 +1542,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
elements.append(element)
|
elements.append(element)
|
||||||
return elements
|
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
|
@classmethod
|
||||||
def is_auto_annotation(cls, element: ifcopenshell.entity_instance):
|
def is_auto_annotation(cls, element: ifcopenshell.entity_instance):
|
||||||
if not (element.is_a("IfcAnnotation") and element.ObjectType in ("GRID", "SECTION", "ELEVATION", "SECTION_LEVEL")):
|
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"
|
element.Name = elevation.Name or "Unnamed"
|
||||||
return element
|
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
|
@classmethod
|
||||||
def set_manual_drawing_reference(cls, element: ifcopenshell.entity_instance) -> None:
|
def set_manual_drawing_reference(cls, element: ifcopenshell.entity_instance) -> None:
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
|
|||||||
Reference in New Issue
Block a user