From f2352e9a1797eaef24158c7f41b9ab4ec471fac8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 16 May 2021 18:26:48 +1000 Subject: [PATCH] Old annotation systems now creates IFC elements again. Also text and misc annotations now work again with new system. See #1153. --- src/blenderbim/blenderbim/bim/handler.py | 4 +- .../bim/module/drawing/annotation.py | 12 +++--- .../blenderbim/bim/module/drawing/operator.py | 16 ++++++- .../bim/module/geometry/operator.py | 5 ++- src/blenderbim/blenderbim/bim/operator.py | 3 +- .../api/geometry/add_representation.py | 37 +++++++++++++++- .../ifcopenshell/util/element.py | 29 ------------- .../ifcopenshell/util/representation.py | 42 +++++++++++++++++++ 8 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/util/representation.py diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 6050f70353..508dcc6958 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -14,14 +14,14 @@ def mode_callback(obj, data): if ( obj.mode != "EDIT" or not obj.data - or not isinstance(obj.data, bpy.types.Mesh) + or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve)) or not obj.BIMObjectProperties.ifc_definition_id or not bpy.context.scene.BIMProjectProperties.is_authoring ): return if obj.data.BIMMeshProperties.ifc_definition_id: representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id) - if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep": + if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]: IfcStore.edited_objs.add(obj.name) elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"): IfcStore.edited_objs.add(obj.name) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 381628877a..7853d424aa 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -17,9 +17,9 @@ class Annotator: @staticmethod def add_text(related_element=None): - curve = bpy.data.curves.new(type="FONT", name="Plan/Annotation/PLAN_VIEW/Text") + curve = bpy.data.curves.new(type="FONT", name="Text") curve.body = "TEXT" - obj = bpy.data.objects.new("IfcAnnotation/Text", curve) + obj = bpy.data.objects.new("Text", curve) obj.matrix_world = bpy.context.scene.camera.matrix_world if related_element is None: location, _, _, _ = Annotator.get_placeholder_coords() @@ -138,12 +138,12 @@ class Annotator: if name in obj.name: return obj if data_type == "mesh": - data = bpy.data.meshes.new("Plan/Annotation/PLAN_VIEW/" + name) + data = bpy.data.meshes.new(name) elif data_type == "curve": - data = bpy.data.curves.new("Plan/Annotation/PLAN_VIEW/" + name, type="CURVE") + data = bpy.data.curves.new(name, type="CURVE") data.dimensions = "3D" data.resolution_u = 2 - obj = bpy.data.objects.new("IfcAnnotation/" + name, data) + obj = bpy.data.objects.new(name, data) collection.objects.link(obj) return obj @@ -164,4 +164,4 @@ class Annotator: return (camera.location + z_offset, camera.location + z_offset + y_offset, camera.location + z_offset + x_offset, - camera.location + z_offset + x_offset + y_offset) \ No newline at end of file + camera.location + z_offset + x_offset + y_offset) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 4475d41851..030e10778c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -3,6 +3,7 @@ import bpy import json import subprocess import webbrowser +import ifcopenshell.util.representation import blenderbim.bim.module.drawing.svgwriter as svgwriter import blenderbim.bim.module.drawing.annotation as annotation from mathutils import Vector, Matrix, Euler, geometry @@ -13,6 +14,7 @@ from ifcopenshell.api.pset.data import Data as PsetData cwd = os.path.dirname(os.path.realpath(__file__)) + class AddDrawing(bpy.types.Operator): bl_idname = "bim.add_drawing" bl_label = "Add Drawing" @@ -69,7 +71,7 @@ class CreateDrawing(bpy.types.Operator): def combine_svgs(self, context, base, annotation): # Hacky :) svg_path = context.scene.BIMProperties.ifc_file[0:-4] + ".svg" - with open(svg_path, 'w') as outfile: + with open(svg_path, "w") as outfile: with open(base) as infile: for line in infile: if "" in line: @@ -197,6 +199,11 @@ class AddAnnotation(bpy.types.Operator): def execute(self, context): if not bpy.context.scene.camera: return {"FINISHED"} + subcontext = ifcopenshell.util.representation.get_context( + IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view + ) + if not subcontext: + return {"FINISHED"} if self.data_type == "text": if bpy.context.selected_objects: for selected_object in bpy.context.selected_objects: @@ -209,7 +216,14 @@ class AddAnnotation(bpy.types.Operator): obj = annotation.Annotator.add_plane_to_annotation(obj) else: obj = annotation.Annotator.add_line_to_annotation(obj) + + if not obj.BIMObjectProperties.ifc_definition_id: + bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id()) + else: + bpy.ops.bim.update_mesh_representation(obj=obj.name) + bpy.ops.object.select_all(action="DESELECT") bpy.context.view_layer.objects.active = obj + obj.select_set(True) bpy.ops.object.mode_set(mode="EDIT") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 88b231c13c..37051e839b 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -3,6 +3,7 @@ import numpy as np import ifcopenshell import ifcopenshell.util.unit import ifcopenshell.util.element +import ifcopenshell.util.representation import logging import ifcopenshell.api from blenderbim.bim.ifc import IfcStore @@ -105,7 +106,7 @@ class AddRepresentation(bpy.types.Operator): return {"FINISHED"} box_context_id = get_context_id("Model", "Box", "MODEL_VIEW") - old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW") + old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW") if ( box_context_id and context_of_items.ContextType == "Model" @@ -358,7 +359,7 @@ class UpdateMeshRepresentation(bpy.types.Operator): new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) box_context_id = get_context_id("Model", "Box", "MODEL_VIEW") - old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW") + old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW") if ( box_context_id and old_box diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 0e37e19ec5..aeff072a13 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -11,6 +11,7 @@ import ifcopenshell import ifcopenshell.util.selector import ifcopenshell.util.geolocation import ifcopenshell.util.element +import ifcopenshell.util.representation import ifcopenshell.util.schema import numpy as np from . import export_ifc @@ -617,7 +618,7 @@ class CutSection(bpy.types.Operator): if not element.Representation: return False for representation in element.Representation.Representations: - if ifcopenshell.util.element.is_representation_of_context( + if ifcopenshell.util.representation.is_representation_of_context( representation, "Plan", "Annotation", camera.data.BIMCameraProperties.target_view ): return True diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index df13173477..00ca5ab0dd 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -48,7 +48,7 @@ class Usecase: for modifier in self.settings["blender_object"].modifiers: if modifier.type == "BOOLEAN": modifier.show_viewport = False - + if self.settings["should_force_triangulation"]: mesh = self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() bm = bmesh.new() @@ -119,6 +119,41 @@ class Usecase: elif self.settings["context"].ContextIdentifier == "SurveyPoints": pass + def create_text_representation(self): + return self.file.createIfcShapeRepresentation( + self.settings["context"], + self.settings["context"].ContextIdentifier, + "Annotation2D", + [self.create_text()], + ) + + def create_text(self): + text = self.settings["geometry"] + if text.align_y in ["TOP_BASELINE", "BOTTOM_BASELINE", "BOTTOM"]: + y = "bottom" + elif text.align_y == "CENTER": + y = "middle" + elif text.align_y == "TOP": + y = "top" + + if text.align_x == "LEFT": + x = "left" + elif text.align_x == "CENTER": + x = "middle" + elif text.align_x == "RIGHT": + x = "right" + + origin = self.file.createIfcAxis2Placement3D( + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.file.createIfcDirection((1.0, 0.0, 0.0)), + ) + + # TODO: Planar extent right now is wrong ... + return self.file.createIfcTextLiteralWithExtent( + text.body, origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), f"{y}-{x}" + ) + def create_variable_representation(self): if self.settings["is_wireframe"]: return self.create_wireframe_representation() diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index dbe28a0bb3..8fb338e957 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -108,24 +108,6 @@ def replace_attribute(element, old, new): element[i] = new_attribute -def is_representation_of_context(representation, context, subcontext=None, target_view=None): - if target_view is not None: - return ( - representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext") - and representation.ContextOfItems.TargetView == target_view - and representation.ContextOfItems.ContextIdentifier == subcontext - and representation.ContextOfItems.ContextType == context - ) - elif subcontext is not None: - return ( - representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext") - and representation.ContextOfItems.ContextIdentifier == subcontext - and representation.ContextOfItems.ContextType == context - ) - elif representation.ContextOfItems.ContextType == context: - return True - - def remove_deep(ifc_file, element): # @todo maybe some sort of try-finally mechanism. ifc_file.batch() @@ -159,14 +141,3 @@ def copy_deep(ifc_file, element): attribute[j] = copy_deep(ifc_file, item) new[i] = attribute return new - - -def get_representation(element, context, subcontext=None, target_view=None): - if element.is_a("IfcProduct") and element.Representation: - for r in element.Representation.Representations: - if is_representation_of_context(r, context, subcontext, target_view): - return r - elif element.is_a("IfcTypeProduct") and element.RepresentationMaps: - for r in element.RepresentationMaps: - if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view): - return r.MappedRepresentation diff --git a/src/ifcopenshell-python/ifcopenshell/util/representation.py b/src/ifcopenshell-python/ifcopenshell/util/representation.py new file mode 100644 index 0000000000..99e608b07f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/representation.py @@ -0,0 +1,42 @@ +def get_context(ifc_file, context, subcontext=None, target_view=None): + if subcontext or target_view: + elements = ifc_file.by_type("IfcGeometricRepresentationSubContext") + else: + elements = ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False) + for element in elements: + if context and element.ContextType != context: + continue + if subcontext and getattr(element, "ContextIdentifier") != subcontext: + continue + if target_view and getattr(element, "TargetView") != target_view: + continue + return element + + +def is_representation_of_context(representation, context, subcontext=None, target_view=None): + if target_view is not None: + return ( + representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext") + and representation.ContextOfItems.TargetView == target_view + and representation.ContextOfItems.ContextIdentifier == subcontext + and representation.ContextOfItems.ContextType == context + ) + elif subcontext is not None: + return ( + representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext") + and representation.ContextOfItems.ContextIdentifier == subcontext + and representation.ContextOfItems.ContextType == context + ) + elif representation.ContextOfItems.ContextType == context: + return True + + +def get_representation(element, context, subcontext=None, target_view=None): + if element.is_a("IfcProduct") and element.Representation: + for r in element.Representation.Representations: + if is_representation_of_context(r, context, subcontext, target_view): + return r + elif element.is_a("IfcTypeProduct") and element.RepresentationMaps: + for r in element.RepresentationMaps: + if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view): + return r.MappedRepresentation