diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index b24d93f953..1b5b99cba7 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -35,7 +35,6 @@ classes = ( operator.ResizeText, operator.AddVariable, operator.RemoveVariable, - operator.PropagateTextData, operator.RemoveDrawing, operator.AddDrawingStyle, operator.RemoveDrawingStyle, @@ -54,6 +53,9 @@ classes = ( operator.CleanWireframes, operator.CopyGrid, operator.AddSectionsAnnotations, + operator.EditText, + operator.DisableEditingText, + operator.EnableEditingText, prop.Variable, prop.Drawing, prop.Schedule, @@ -81,6 +83,7 @@ classes = ( def register(): bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties) + bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties) bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties) bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad) bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py new file mode 100644 index 0000000000..7da44213ce --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py @@ -0,0 +1,47 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell.util.representation +import blenderbim.tool as tool + + +def refresh(): + TextData.is_loaded = False + + +class TextData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"attributes": cls.attributes()} + cls.is_loaded = True + + @classmethod + def attributes(cls): + element = tool.Ifc.get_entity(bpy.context.active_object) + if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]: + return [] + rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation") + text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0] + return [ + {"name": "Literal", "value": text_literal.Literal}, + {"name": "BoxAlignment", "value": text_literal.BoxAlignment}, + ] diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 1271353d0a..a3d9a3df38 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -1228,6 +1228,90 @@ class SectionViewDecorator(LevelDecorator): self.draw_lines(context, obj, verts, [(0, 1)]) +class TextDecorator(BaseDecorator): + """Decorator for text objects + - draws the text next to the origin + """ + + objecttype = "TEXT" + + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ + #define ARROW_ANGLE PI / 12.0 + #define ARROW_SIZE 16.0 + """ + ) + + GEOM_GLSL = """ + uniform vec2 winsize; + uniform float viewportDrawingScale; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + + void main() { + vec4 clip2win = matCLIP2WIN(); + vec4 win2clip = matWIN2CLIP(); + + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + + vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); + vec4 edge = p1w - p0w, dir = normalize(edge); + + vec4 p; + + vec4 head[3]; + arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head); + + // start edge arrow + gl_Position = p0; + EmitVertex(); + p = p0w + head[1]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w + head[2]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + gl_Position = p0; + EmitVertex(); + EndPrimitive(); + + // end edge arrow + gl_Position = p1; + EmitVertex(); + p = p1w - head[1]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w - head[2]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + gl_Position = p1; + EmitVertex(); + EndPrimitive(); + + // stem, with gaps for arrows + p = p0w + head[0]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w - head[0]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } + """ + + def decorate(self, context, obj): + self.draw_labels(context, obj) + + def draw_labels(self, context, obj): + region = context.region + region3d = context.region_data + dir = Vector((1, 0)) + pos = location_3d_to_region_2d(region, region3d, obj.matrix_world.translation) + self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False) + + class DecorationsHandler: decorators_classes = [ DimensionDecorator, @@ -1241,6 +1325,7 @@ class DecorationsHandler: StairDecorator, BreakDecorator, SectionViewDecorator, + TextDecorator, ] installed = None diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 371f22ef7e..e43261dba5 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -32,6 +32,7 @@ import ifcopenshell.util.selector import ifcopenshell.util.representation import blenderbim.bim.schema import blenderbim.tool as tool +import blenderbim.core.drawing as core import blenderbim.bim.module.drawing.svgwriter as svgwriter import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.sheeter as sheeter @@ -57,6 +58,13 @@ def open_with_user_command(user_command, path): webbrowser.open("file://" + path) +class Operator: + def execute(self, context): + IfcStore.execute_ifc_operator(self, context) + blenderbim.bim.handler.refresh_ui_data() + return {"FINISHED"} + + class AddDrawing(bpy.types.Operator): bl_idname = "bim.add_drawing" bl_label = "Add Drawing" @@ -79,7 +87,7 @@ class AddDrawing(bpy.types.Operator): view_collection = bpy.data.collections.new("IfcGroup/" + new.name) views_collection.children.link(view_collection) camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name)) - camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin + camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera.data.type = "ORTHO" camera.data.ortho_scale = 50 # The default of 6m is too small camera.data.clip_end = 10 # A slightly more reasonable default @@ -573,28 +581,45 @@ class AddAnnotation(bpy.types.Operator): ) if not subcontext: return {"FINISHED"} - if self.data_type == "text": - if context.selected_objects: - for selected_object in context.selected_objects: - obj = annotation.Annotator.add_text(context, related_element=selected_object) - else: - obj = annotation.Annotator.add_text(context) - else: - obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context) - if self.obj_name == "Break": - obj = annotation.Annotator.add_plane_to_annotation(obj, context) - else: - obj = annotation.Annotator.add_line_to_annotation(obj, context) + # TODO: reimplement bulk smart tagging + # if self.data_type == "text": + # if context.selected_objects: + # for selected_object in context.selected_objects: + # obj = annotation.Annotator.add_text(context, related_element=selected_object) + # else: + # obj = annotation.Annotator.add_text(context) + # else: + obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context) + if self.object_type == "BREAKLINE": + obj = annotation.Annotator.add_plane_to_annotation(obj, context) + elif self.object_type != "TEXT": + obj = annotation.Annotator.add_line_to_annotation(obj, context) if not obj.BIMObjectProperties.ifc_definition_id: - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id()) + ifc_representation_class = "" + if self.object_type == "TEXT": + ifc_representation_class = "IfcTextLiteral" + elif self.object_type == "TEXT_LEADER": + ifc_representation_class = "IfcGeometricCurveSet/IfcTextLiteral" + bpy.ops.bim.assign_class( + obj=obj.name, + ifc_class="IfcAnnotation", + context_id=subcontext.id(), + ifc_representation_class=ifc_representation_class, + ) + element = tool.Ifc.get_entity(obj) + element.ObjectType = self.object_type + camera = tool.Ifc.get_entity(context.scene.camera) + group = [r for r in camera.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup + bpy.ops.bim.assign_group(product=obj.name, group=group.id()) else: bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.object.select_all(action="DESELECT") context.view_layer.objects.active = obj obj.select_set(True) - bpy.ops.object.mode_set(mode="EDIT") + if obj.data: + bpy.ops.object.mode_set(mode="EDIT") return {"FINISHED"} @@ -888,29 +913,6 @@ class RemoveVariable(bpy.types.Operator): return {"FINISHED"} -class PropagateTextData(bpy.types.Operator): - bl_idname = "bim.propagate_text_data" - bl_label = "Propagate Text Data" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - source = context.active_object - for obj in context.selected_objects: - if obj == source: - continue - obj.data.body = source.data.body - obj.data.align_x = source.data.align_x - obj.data.align_y = source.data.align_y - obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size - obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol - obj.data.BIMTextProperties.variables.clear() - for variable in source.data.BIMTextProperties.variables: - new_variable = obj.data.BIMTextProperties.variables.add() - new_variable.name = variable.name - new_variable.prop_key = variable.prop_key - return {"FINISHED"} - - class RemoveDrawing(bpy.types.Operator): bl_idname = "bim.remove_drawing" bl_label = "Remove Drawing" @@ -1380,3 +1382,30 @@ class AddSectionsAnnotations(bpy.types.Operator): view_coll.objects.link(obj) return {"FINISHED"} + + +class EditText(bpy.types.Operator, Operator): + bl_idname = "bim.edit_text" + bl_label = "Edit Text" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.edit_text(tool.Ifc, tool.Drawing, obj=context.active_object) + + +class EnableEditingText(bpy.types.Operator, Operator): + bl_idname = "bim.enable_editing_text" + bl_label = "Enable Editing Text" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.enable_editing_text(tool.Drawing, obj=context.active_object) + + +class DisableEditingText(bpy.types.Operator, Operator): + bl_idname = "bim.disable_editing_text" + bl_label = "Disable Editing Text" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.disable_editing_text(tool.Drawing, obj=context.active_object) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index fdd8eb304d..bf8764ebac 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -169,6 +169,10 @@ def refreshTitleblocks(self, context): def toggleDecorations(self, context): toggle = self.should_draw_decorations if toggle: + # TODO: design a proper text variable templating renderer + collection = context.scene.camera.users_collection[0] + for obj in collection.objects: + tool.Drawing.update_text_value(obj) decoration.DecorationsHandler.install(context) else: decoration.DecorationsHandler.uninstall() @@ -353,6 +357,9 @@ class BIMCameraProperties(PropertyGroup): class BIMTextProperties(PropertyGroup): + is_editing: BoolProperty(name="Is Editing", default=False) + attributes: CollectionProperty(name="Attributes", type=Attribute) + value: StringProperty(name="Value", default="TEXT") font_size: EnumProperty( items=[ ("1.8", "1.8 - Small", ""), @@ -361,6 +368,7 @@ class BIMTextProperties(PropertyGroup): ("5.0", "5.0 - Header", ""), ("7.0", "7.0 - Title", ""), ], + default="2.5", update=refreshFontSize, name="Font Size", ) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index f3a9c3afed..1b4532758c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -24,6 +24,7 @@ import pystache import xml.etree.ElementTree as ET import svgwrite import ifcopenshell +import ifcopenshell.util.representation import blenderbim.tool as tool import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.annotation as annotation @@ -208,8 +209,7 @@ class SvgWriter: for obj_data in self.annotations.get("solid_objs", []): self.draw_line_annotation(obj_data, ["solid"]) - if self.annotations.get("leader_obj"): - self.draw_line_annotation(self.annotations["leader_obj"], ["leader"]) + self.draw_leader_annotations() if self.annotations.get("plan_level_obj"): matrix_world = self.annotations["plan_level_obj"].matrix_world @@ -456,72 +456,99 @@ class SvgWriter: self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)) ) + def draw_leader_annotations(self): + for obj in self.annotations.get("leader_objs", []): + self.draw_line_annotation((obj, obj.data), ["leader"]) + spline = obj.data.splines[0] + spline_points = spline.bezier_points if spline.bezier_points else spline.points + if spline_points: + position = obj.matrix_world @ spline_points[0].co.xyz + else: + position = Vector((0, 0, 0)) + self.draw_text_annotation(obj, position) + def draw_text_annotations(self): + for text_obj in self.annotations.get("text_objs", []): + self.draw_text_annotation(text_obj, text_obj.location) + + def draw_text_annotation(self, text_obj, position): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 + element = tool.Ifc.get_entity(text_obj) + rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation") + text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0] - for text_obj in self.annotations.get("text_objs", []): - text_position = self.project_point_onto_camera(text_obj.location) - text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y))) + text_position = self.project_point_onto_camera(position) + text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y))) - local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0)) - projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis) - angle = math.degrees( - (Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed( - Vector((1, 0)) - ) + local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0)) + projected_x_axis = self.project_point_onto_camera(position + local_x_axis) + angle = math.degrees( + (Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed( + Vector((1, 0)) + ) + ) + + transform = "rotate({}, {}, {})".format( + angle, + (text_position * self.scale)[0], + (text_position * self.scale)[1], + ) + + if text_obj.BIMTextProperties.symbol != "None": + self.svg.add( + self.svg.use("#{}".format(text_obj.BIMTextProperties.symbol), insert=tuple(text_position * self.scale)) ) - transform = "rotate({}, {}, {})".format( - angle, - (text_position * self.scale)[0], - (text_position * self.scale)[1], + if text_literal.BoxAlignment == "top-left": + alignment_baseline = "hanging" + text_anchor = "start" + elif text_literal.BoxAlignment == "top-middle": + alignment_baseline = "hanging" + text_anchor = "middle" + elif text_literal.BoxAlignment == "top-right": + alignment_baseline = "hanging" + text_anchor = "end" + elif text_literal.BoxAlignment == "middle-left": + alignment_baseline = "middle" + text_anchor = "start" + elif text_literal.BoxAlignment == "center": + alignment_baseline = "middle" + text_anchor = "middle" + elif text_literal.BoxAlignment == "middle-right": + alignment_baseline = "middle" + text_anchor = "end" + elif text_literal.BoxAlignment == "bottom-left": + alignment_baseline = "baseline" + text_anchor = "start" + elif text_literal.BoxAlignment == "bottom-middle": + alignment_baseline = "baseline" + text_anchor = "middle" + elif text_literal.BoxAlignment == "bottom-right": + alignment_baseline = "baseline" + text_anchor = "end" + + text_body = text_literal.Literal + if text_obj.name in self.annotations.get("template_variables", {}): + text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name]) + + for line_number, text_line in enumerate(text_body.split("\n")): + self.svg.add( + self.svg.text( + text_line, + insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))), + class_=" ".join(self.get_attribute_classes(text_obj)), + **{ + "font-size": annotation.Annotator.get_svg_text_size(text_obj.BIMTextProperties.font_size), + "font-family": "OpenGost Type B TT", + "text-anchor": text_anchor, + "alignment-baseline": alignment_baseline, + "dominant-baseline": alignment_baseline, + "transform": transform, + }, + ) ) - if text_obj.data.BIMTextProperties.symbol != "None": - self.svg.add( - self.svg.use( - "#{}".format(text_obj.data.BIMTextProperties.symbol), insert=tuple(text_position * self.scale) - ) - ) - - if text_obj.data.align_x == "CENTER": - text_anchor = "middle" - elif text_obj.data.align_x == "RIGHT": - text_anchor = "end" - else: - text_anchor = "start" - - if text_obj.data.align_y == "CENTER": - alignment_baseline = "middle" - elif text_obj.data.align_y == "TOP": - alignment_baseline = "hanging" - else: - alignment_baseline = "baseline" - - text_body = text_obj.data.body - if text_obj.name in self.annotations.get("template_variables", {}): - text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name]) - - for line_number, text_line in enumerate(text_body.split("\n")): - self.svg.add( - self.svg.text( - text_line, - insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))), - class_=" ".join(self.get_attribute_classes(text_obj)), - **{ - "font-size": annotation.Annotator.get_svg_text_size( - text_obj.data.BIMTextProperties.font_size - ), - "font-family": "OpenGost Type B TT", - "text-anchor": text_anchor, - "alignment-baseline": alignment_baseline, - "dominant-baseline": alignment_baseline, - "transform": transform, - }, - ) - ) - def draw_break_annotations(self, break_obj): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index c67aee063e..d0b3485270 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -17,7 +17,10 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.bim.helper +import blenderbim.tool as tool from bpy.types import Panel +from blenderbim.bim.module.drawing.data import TextData class BIM_PT_camera(Panel): @@ -233,39 +236,57 @@ class BIM_PT_sheets(Panel): class BIM_PT_text(Panel): - bl_label = "Text Paper Space" + bl_label = "IFC Text" bl_idname = "BIM_PT_text" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "data" + bl_context = "object" @classmethod def poll(cls, context): - return type(context.curve) is bpy.types.TextCurve + if not tool.Ifc.get() or not context.active_object: + return + element = tool.Ifc.get_entity(context.active_object) + if not element: + return + return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"] def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.active_object.data.BIMTextProperties + if not TextData.is_loaded: + TextData.load() - row = layout.row() - row.operator("bim.propagate_text_data") + self.layout.use_property_split = True + props = context.active_object.BIMTextProperties - row = layout.row() + if props.is_editing: + row = self.layout.row(align=True) + row.operator("bim.edit_text", icon="CHECKMARK") + row.operator("bim.disable_editing_text", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(props.attributes, self.layout) + else: + row = self.layout.row() + row.operator("bim.enable_editing_text", icon="GREASEPENCIL") + + for attribute in TextData.data["attributes"]: + row = self.layout.row(align=True) + row.label(text=attribute["name"]) + row.label(text=attribute["value"]) + + row = self.layout.row() row.prop(props, "font_size") - row = layout.row() + row = self.layout.row() row.prop(props, "symbol") - row = layout.row() + row = self.layout.row() row.prop(props, "related_element") - row = layout.row() + row = self.layout.row() row.operator("bim.add_variable") for index, variable in enumerate(props.variables): - row = layout.row(align=True) + row = self.layout.row(align=True) row.prop(variable, "name") row.operator("bim.remove_variable", icon="X", text="").index = index - row = layout.row() + row = self.layout.row() row.prop(variable, "prop_key") diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 588e790c9e..630177f34d 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -185,6 +185,7 @@ class UpdateRepresentation(bpy.types.Operator): product, old_representation ) representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product) + representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation) new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py new file mode 100644 index 0000000000..56fa27d524 --- /dev/null +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -0,0 +1,36 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +def enable_editing_text(drawing, obj=None): + drawing.enable_editing_text(obj) + drawing.import_text_attributes(obj) + + +def disable_editing_text(drawing, obj=None): + drawing.disable_editing_text(obj) + + +def edit_text(ifc, drawing, obj=None): + ifc.run( + "drawing.edit_text_literal", + text_literal=drawing.get_text_literal(obj), + attributes=drawing.export_text_literal_attributes(obj), + ) + drawing.update_text_value(obj) + drawing.disable_editing_text(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index d3741a2dbe..9af5056751 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -70,6 +70,16 @@ class Context: def set_context(cls, context): pass +@interface +class Drawing: + def disable_editing_text(cls, obj): pass + def enable_editing_text(cls, obj): pass + def export_text_literal_attributes(cls, obj): pass + def get_text_literal(cls, obj): pass + def import_text_attributes(cls, obj): pass + def update_text_value(cls, obj): pass + + @interface class Geometry: def change_object_data(cls, obj, data, is_global=False): pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 679ae4671e..c5672ddfaf 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -21,6 +21,7 @@ from blenderbim.tool.blender import Blender from blenderbim.tool.brick import Brick from blenderbim.tool.collector import Collector from blenderbim.tool.context import Context +from blenderbim.tool.drawing import Drawing from blenderbim.tool.geometry import Geometry from blenderbim.tool.ifc import Ifc from blenderbim.tool.material import Material diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py new file mode 100644 index 0000000000..bf885b633b --- /dev/null +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -0,0 +1,61 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import blenderbim.core.tool +import blenderbim.tool as tool +import ifcopenshell.util.representation + + +class Drawing(blenderbim.core.tool.Drawing): + @classmethod + def disable_editing_text(cls, obj): + obj.BIMTextProperties.is_editing = False + + @classmethod + def enable_editing_text(cls, obj): + obj.BIMTextProperties.is_editing = True + + @classmethod + def export_text_literal_attributes(cls, obj): + return blenderbim.bim.helper.export_attributes(obj.BIMTextProperties.attributes) + + @classmethod + def get_text_literal(cls, obj): + element = tool.Ifc.get_entity(obj) + if not element: + return + rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation") + if not rep: + return + items = [i for i in rep.Items if i.is_a("IfcTextLiteral")] + if items: + return items[0] + + @classmethod + def import_text_attributes(cls, obj): + props = obj.BIMTextProperties + props.attributes.clear() + text = cls.get_text_literal(obj) + blenderbim.bim.helper.import_attributes2(text, props.attributes) + + @classmethod + def update_text_value(cls, obj): + element = cls.get_text_literal(obj) + if element: + obj.BIMTextProperties.value = element.Literal diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 67edbb8ec9..8a6f3a94ba 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -105,6 +105,12 @@ class Geometry(blenderbim.core.tool.Geometry): @classmethod def get_ifc_representation_class(cls, element, representation): + if element.is_a("IfcAnnotation"): + if element.ObjectType == "TEXT": + return "IfcTextLiteral" + elif element.ObjectType == "TEXT_LEADER": + return "IfcGeometricCurveSet/IfcTextLiteral" + material = ifcopenshell.util.element.get_material(element) if material and material.is_a("IfcMaterialProfileSetUsage"): return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" @@ -151,6 +157,12 @@ class Geometry(blenderbim.core.tool.Geometry): def get_styles(cls, obj): return [tool.Style.get_style(s.material) for s in obj.material_slots if s.material] + @classmethod + def get_text_literal(cls, representation): + texts = [i for i in representation.Items if i.is_a("IfcTextLiteral")] + if texts: + return texts[0] + @classmethod def get_total_representation_items(cls, obj): return max(1, len(obj.material_slots)) diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index 9998bc3007..8970f6c5d8 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -5,6 +5,7 @@ markers = bimtester brick context + drawing geometry material misc diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 2757c36d32..2c2c3d7ca2 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -63,6 +63,13 @@ def context(): prophet.verify() +@pytest.fixture +def drawing(): + prophet = Prophecy(blenderbim.core.tool.Drawing) + yield prophet + prophet.verify() + + @pytest.fixture def geometry(): prophet = Prophecy(blenderbim.core.tool.Geometry) diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py new file mode 100644 index 0000000000..b16f72e53c --- /dev/null +++ b/src/blenderbim/test/core/test_drawing.py @@ -0,0 +1,43 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import blenderbim.core.drawing as subject +from test.core.bootstrap import ifc, drawing + + +class TestEnableEditingText: + def test_run(self, drawing): + drawing.enable_editing_text("obj").should_be_called() + drawing.import_text_attributes("obj").should_be_called() + subject.enable_editing_text(drawing, obj="obj") + + +class TestDisableEditingText: + def test_run(self, drawing): + drawing.disable_editing_text("obj").should_be_called() + subject.disable_editing_text(drawing, obj="obj") + + +class TestEditText: + def test_run(self, ifc, drawing): + drawing.get_text_literal("obj").should_be_called().will_return("text") + drawing.export_text_literal_attributes("obj").should_be_called().will_return("attributes") + ifc.run("drawing.edit_text_literal", text_literal="text", attributes="attributes").should_be_called() + drawing.update_text_value("obj").should_be_called() + drawing.disable_editing_text("obj").should_be_called() + subject.edit_text(ifc, drawing, obj="obj") diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py new file mode 100644 index 0000000000..4ef3e4c2d8 --- /dev/null +++ b/src/blenderbim/test/tool/test_drawing.py @@ -0,0 +1,96 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import blenderbim.core.tool +import blenderbim.tool as tool +from test.bim.bootstrap import NewFile +from blenderbim.tool.drawing import Drawing as subject + + +class TestImplementsTool(NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.Drawing) + + +class TestDisableEditingText(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + obj.BIMTextProperties.is_editing = True + subject.disable_editing_text(obj) + assert obj.BIMTextProperties.is_editing == False + + +class TestEnableEditingText(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + subject.enable_editing_text(obj) + assert obj.BIMTextProperties.is_editing == True + + +class TestExportTextLiteralAttributes(NewFile): + def test_run(self): + TestImportTextAttributes().test_run() + assert subject.export_text_literal_attributes(bpy.data.objects.get("Object")) == { + "Literal": "Literal", + "Path": "RIGHT", + "BoxAlignment": "BoxAlignment", + } + + +class TestGetTextLiteral(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + obj = bpy.data.objects.new("Object", None) + element = ifc.createIfcAnnotation() + element.Representation = ifc.createIfcProductDefinitionShape() + context = ifc.createIfcGeometricRepresentationSubContext(ContextType="Plan", ContextIdentifier="Annotation") + item = ifc.createIfcTextLiteralWithExtent(Literal="Literal", Path="RIGHT", BoxAlignment="BoxAlignment") + representation = ifc.createIfcShapeRepresentation(ContextOfItems=context, Items=[item]) + element.Representation.Representations = [representation] + tool.Ifc.link(element, obj) + assert subject.get_text_literal(obj) == item + + +class TestImportTextAttributes(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + obj = bpy.data.objects.new("Object", None) + element = ifc.createIfcAnnotation() + element.Representation = ifc.createIfcProductDefinitionShape() + context = ifc.createIfcGeometricRepresentationSubContext(ContextType="Plan", ContextIdentifier="Annotation") + item = ifc.createIfcTextLiteralWithExtent(Literal="Literal", Path="RIGHT", BoxAlignment="BoxAlignment") + representation = ifc.createIfcShapeRepresentation(ContextOfItems=context, Items=[item]) + element.Representation.Representations = [representation] + tool.Ifc.link(element, obj) + subject.import_text_attributes(obj) + props = obj.BIMTextProperties + assert props.attributes.get("Literal").string_value == "Literal" + assert props.attributes.get("Path").enum_value == "RIGHT" + assert props.attributes.get("BoxAlignment").string_value == "BoxAlignment" + + +class TestUpdateTextValue(NewFile): + def test_run(self): + TestGetTextLiteral().test_run() + obj = bpy.data.objects.get("Object") + subject.update_text_value(obj) + assert obj.BIMTextProperties.value == "Literal" diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index 88f409d93b..5f857685ef 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -178,7 +178,16 @@ class TestGetStyles(NewFile): tool.Ifc.link(style, material) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) obj.data.materials.append(material) - subject.get_styles(obj) == [style] + assert subject.get_styles(obj) == [style] + + +class TestGetTextLiteral(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + item = ifc.createIfcTextLiteralWithExtent() + representation = ifc.createIfcShapeRepresentation(Items=[item]) + assert subject.get_text_literal(representation) == item class TestGetCartesianPointCoordinateOffset(NewFile): @@ -490,6 +499,20 @@ class TestGetIfcRepresentationClass(NewFile): subject.get_ifc_representation_class(element, representation) == "IfcExtrudedAreaSolid/IfcCircleProfileDef" ) + def test_detecting_text_representations(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcAnnotation() + element.ObjectType = "TEXT" + assert subject.get_ifc_representation_class(element, None) == "IfcTextLiteral" + + def test_detecting_text_and_geometric_representations(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcAnnotation() + element.ObjectType = "TEXT_LEADER" + assert subject.get_ifc_representation_class(element, None) == "IfcGeometricCurveSet/IfcTextLiteral" + def test_returning_null_for_non_parametric_representations(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) diff --git a/src/blenderbim/test/tool/test_owner.py b/src/blenderbim/test/tool/test_owner.py index ff755a443f..41d398f93a 100644 --- a/src/blenderbim/test/tool/test_owner.py +++ b/src/blenderbim/test/tool/test_owner.py @@ -18,18 +18,18 @@ import bpy import ifcopenshell -import test.bim.bootstrap import blenderbim.core.tool import blenderbim.tool as tool +from test.bim.bootstrap import NewFile from blenderbim.tool.owner import Owner as subject -class TestImplementsTool(test.bim.bootstrap.NewFile): +class TestImplementsTool(NewFile): def test_run(self): assert isinstance(subject(), blenderbim.core.tool.Owner) -class TestSetUser(test.bim.bootstrap.NewFile): +class TestSetUser(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) @@ -38,7 +38,7 @@ class TestSetUser(test.bim.bootstrap.NewFile): assert bpy.context.scene.BIMOwnerProperties.active_user_id == user.id() -class TestGetUser(test.bim.bootstrap.NewFile): +class TestGetUser(NewFile): def test_run(self): assert subject.get_user() is None TestSetUser().test_run() @@ -56,14 +56,14 @@ class TestGetUser(test.bim.bootstrap.NewFile): assert subject.get_user() == user2 -class TestClearUser(test.bim.bootstrap.NewFile): +class TestClearUser(NewFile): def test_run(self): TestSetUser().test_run() subject.clear_user() assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0 -class TestSetAddress(test.bim.bootstrap.NewFile): +class TestSetAddress(NewFile): def test_run(self): ifc = ifcopenshell.file() address = ifc.createIfcPostalAddress() @@ -71,7 +71,7 @@ class TestSetAddress(test.bim.bootstrap.NewFile): assert bpy.context.scene.BIMOwnerProperties.active_address_id == address.id() -class TestImportAddressAttributes(test.bim.bootstrap.NewFile): +class TestImportAddressAttributes(NewFile): def test_importing_a_postal_address(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -152,7 +152,7 @@ class TestImportAddressAttributes(test.bim.bootstrap.NewFile): assert len(props.messaging_ids) == 0 -class TestClearAddress(test.bim.bootstrap.NewFile): +class TestClearAddress(NewFile): def test_run(self): ifc = ifcopenshell.file() address = ifc.createIfcPostalAddress() @@ -161,7 +161,7 @@ class TestClearAddress(test.bim.bootstrap.NewFile): assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0 -class TestGetAddress(test.bim.bootstrap.NewFile): +class TestGetAddress(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -170,7 +170,7 @@ class TestGetAddress(test.bim.bootstrap.NewFile): assert subject().get_address() == address -class TestExportAttributes(test.bim.bootstrap.NewFile): +class TestExportAttributes(NewFile): def test_exporting_a_postal_address(self): TestImportAddressAttributes().test_importing_a_postal_address() assert subject().export_address_attributes() == { @@ -201,7 +201,7 @@ class TestExportAttributes(test.bim.bootstrap.NewFile): } -class TestAddAddressAttribute(test.bim.bootstrap.NewFile): +class TestAddAddressAttribute(NewFile): def test_run(self): subject().add_address_attribute("AddressLines") subject().add_address_attribute("TelephoneNumbers") @@ -216,7 +216,7 @@ class TestAddAddressAttribute(test.bim.bootstrap.NewFile): assert len(props.messaging_ids) == 1 -class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile): +class TestRemoveAddressAttribute(NewFile): TestAddAddressAttribute().test_run() subject().remove_address_attribute("AddressLines", 0) subject().remove_address_attribute("TelephoneNumbers", 0) @@ -231,14 +231,14 @@ class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile): assert len(props.messaging_ids) == 0 -class TestSetOrganisation(test.bim.bootstrap.NewFile): +class TestSetOrganisation(NewFile): def test_run(self): organisation = ifcopenshell.file().createIfcOrganization() subject().set_organisation(organisation) assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id() -class TestImportOrganisationAttributes(test.bim.bootstrap.NewFile): +class TestImportOrganisationAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -269,7 +269,7 @@ class TestImportOrganisationAttributes(test.bim.bootstrap.NewFile): assert props.organisation_attributes.get("Description").string_value == "" -class TestClearOrganisation(test.bim.bootstrap.NewFile): +class TestClearOrganisation(NewFile): def test_run(self): props = bpy.context.scene.BIMOwnerProperties props.active_organisation_id = 1 @@ -277,7 +277,7 @@ class TestClearOrganisation(test.bim.bootstrap.NewFile): assert props.active_organisation_id == 0 -class TestExportOrganisationAttributes(test.bim.bootstrap.NewFile): +class TestExportOrganisationAttributes(NewFile): def test_run(self): TestImportOrganisationAttributes().test_run() assert subject().export_organisation_attributes() == { @@ -287,7 +287,7 @@ class TestExportOrganisationAttributes(test.bim.bootstrap.NewFile): } -class TestGetOrganisation(test.bim.bootstrap.NewFile): +class TestGetOrganisation(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -296,14 +296,14 @@ class TestGetOrganisation(test.bim.bootstrap.NewFile): assert subject().get_organisation() == organisation -class TestSetPerson(test.bim.bootstrap.NewFile): +class TestSetPerson(NewFile): def test_run(self): person = ifcopenshell.file().createIfcPerson() subject().set_person(person) assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() -class TestImportPersonAttributes(test.bim.bootstrap.NewFile): +class TestImportPersonAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -346,7 +346,7 @@ class TestImportPersonAttributes(test.bim.bootstrap.NewFile): assert props.person_attributes.get("GivenName").string_value == "" -class TestClearPerson(test.bim.bootstrap.NewFile): +class TestClearPerson(NewFile): def test_run(self): props = bpy.context.scene.BIMOwnerProperties props.active_person_id = 1 @@ -354,7 +354,7 @@ class TestClearPerson(test.bim.bootstrap.NewFile): assert props.active_person_id == 0 -class TestExportPersonAttributes(test.bim.bootstrap.NewFile): +class TestExportPersonAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -385,7 +385,7 @@ class TestExportPersonAttributes(test.bim.bootstrap.NewFile): assert result["SuffixTitles"] is None -class TestGetPerson(test.bim.bootstrap.NewFile): +class TestGetPerson(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -394,7 +394,7 @@ class TestGetPerson(test.bim.bootstrap.NewFile): assert subject().get_person() == person -class TestAddPersonAttribute(test.bim.bootstrap.NewFile): +class TestAddPersonAttribute(NewFile): def test_run(self): subject().add_person_attribute("MiddleNames") subject().add_person_attribute("PrefixTitles") @@ -405,7 +405,7 @@ class TestAddPersonAttribute(test.bim.bootstrap.NewFile): assert len(props.suffix_titles) == 1 -class TestRemovePersonAttribute(test.bim.bootstrap.NewFile): +class TestRemovePersonAttribute(NewFile): def test_run(self): subject().add_person_attribute("MiddleNames") subject().remove_person_attribute("MiddleNames", 0) @@ -419,14 +419,14 @@ class TestRemovePersonAttribute(test.bim.bootstrap.NewFile): assert len(props.suffix_titles) == 0 -class TestSetRole(test.bim.bootstrap.NewFile): +class TestSetRole(NewFile): def test_run(self): role = ifcopenshell.file().createIfcActorRole() subject().set_role(role) assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id() -class TestImportRoleAttributes(test.bim.bootstrap.NewFile): +class TestImportRoleAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -454,7 +454,7 @@ class TestImportRoleAttributes(test.bim.bootstrap.NewFile): assert props.role_attributes.get("Role").enum_value == "ARCHITECT" -class TestClearRole(test.bim.bootstrap.NewFile): +class TestClearRole(NewFile): def test_run(self): role = ifcopenshell.file().createIfcActorRole() subject().set_role(role) @@ -462,7 +462,7 @@ class TestClearRole(test.bim.bootstrap.NewFile): assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0 -class TestGetRole(test.bim.bootstrap.NewFile): +class TestGetRole(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) @@ -471,7 +471,7 @@ class TestGetRole(test.bim.bootstrap.NewFile): assert subject().get_role() == role -class TestExportRoleAttributes(test.bim.bootstrap.NewFile): +class TestExportRoleAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) diff --git a/src/ifcopenshell-python/ifcopenshell/api/drawing/edit_text_literal.py b/src/ifcopenshell-python/ifcopenshell/api/drawing/edit_text_literal.py new file mode 100644 index 0000000000..b84e547784 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/drawing/edit_text_literal.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"text_literal": None, "attributes": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["text_literal"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 906a06e610..d9138a3284 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -28,8 +28,11 @@ class Usecase: # IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef # IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids # IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage + # IfcGeometricCurveSet/IfcTextLiteral + # IfcTextLiteral "ifc_representation_class": None, # Whether to cast a mesh into a particular class "profile_set_usage": None, # The material profile set if the extrusion requires it + "text_literal": None, # The text literal if the representation requires it } self.ifc_vertices = [] for key, value in settings.items(): @@ -117,9 +120,16 @@ class Usecase: return self.create_lighting_representation() def create_plan_representation(self): - if self.settings["context"].ContextIdentifier == "Annotation": - if isinstance(self.settings["geometry"], bpy.types.TextCurve): - return self.create_text_representation() + if self.settings["ifc_representation_class"] == "IfcTextLiteral": + return self.create_text_representation() + elif self.settings["ifc_representation_class"] == "IfcGeometricCurveSet/IfcTextLiteral": + shape_representation = self.create_geometric_curve_set_representation(is_2d=True) + shape_representation.RepresentationType = "Annotation2D" + items = list(shape_representation.Items) + items.append(self.create_text()) + shape_representation.Items = items + return shape_representation + elif self.settings["context"].ContextIdentifier == "Annotation": shape_representation = self.create_geometric_curve_set_representation(is_2d=True) shape_representation.RepresentationType = "Annotation2D" return shape_representation @@ -143,8 +153,6 @@ class Usecase: elif self.settings["context"].ContextIdentifier == "SurveyPoints": pass else: - if isinstance(self.settings["geometry"], bpy.types.TextCurve): - return self.create_text_representation() shape_representation = self.create_geometric_curve_set_representation(is_2d=True) shape_representation.RepresentationType = "Annotation2D" return shape_representation @@ -180,21 +188,8 @@ class Usecase: ) 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" - + if self.settings["text_literal"]: + return self.settings["text_literal"] origin = self.file.createIfcAxis2Placement3D( self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), self.file.createIfcDirection((0.0, 0.0, 1.0)), @@ -203,7 +198,7 @@ class Usecase: # TODO: Planar extent right now is wrong ... return self.file.createIfcTextLiteralWithExtent( - text.body, origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), f"{y}-{x}" + "TEXT", origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), "bottom-left" ) def create_variable_representation(self): diff --git a/src/ifcopenshell-python/test/api/drawing/__init__.py b/src/ifcopenshell-python/test/api/drawing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/drawing/test_edit_text_literal.py b/src/ifcopenshell-python/test/api/drawing/test_edit_text_literal.py new file mode 100644 index 0000000000..967cf1d705 --- /dev/null +++ b/src/ifcopenshell-python/test/api/drawing/test_edit_text_literal.py @@ -0,0 +1,15 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestEditTextLiteral(test.bootstrap.IFC4): + def test_run(self): + text = self.file.createIfcTextLiteralWithExtent() + ifcopenshell.api.run("drawing.edit_text_literal", self.file, text_literal=text, attributes={ + "Literal": "Literal", + "Path": "RIGHT", + "BoxAlignment": "middle", + }) + assert text.Literal == "Literal" + assert text.Path == "RIGHT" + assert text.BoxAlignment == "middle"