Rebuild text annotations to have leaders in the same object and use text literals. See #1153.

This commit is contained in:
Dion Moult
2021-11-16 08:54:16 +11:00
parent fcfdc179df
commit 43869c33f6
23 changed files with 694 additions and 163 deletions
@@ -35,7 +35,6 @@ classes = (
operator.ResizeText, operator.ResizeText,
operator.AddVariable, operator.AddVariable,
operator.RemoveVariable, operator.RemoveVariable,
operator.PropagateTextData,
operator.RemoveDrawing, operator.RemoveDrawing,
operator.AddDrawingStyle, operator.AddDrawingStyle,
operator.RemoveDrawingStyle, operator.RemoveDrawingStyle,
@@ -54,6 +53,9 @@ classes = (
operator.CleanWireframes, operator.CleanWireframes,
operator.CopyGrid, operator.CopyGrid,
operator.AddSectionsAnnotations, operator.AddSectionsAnnotations,
operator.EditText,
operator.DisableEditingText,
operator.EnableEditingText,
prop.Variable, prop.Variable,
prop.Drawing, prop.Drawing,
prop.Schedule, prop.Schedule,
@@ -81,6 +83,7 @@ classes = (
def register(): def register():
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties) 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.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad) bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler) bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
@@ -0,0 +1,47 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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},
]
@@ -1228,6 +1228,90 @@ class SectionViewDecorator(LevelDecorator):
self.draw_lines(context, obj, verts, [(0, 1)]) 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: class DecorationsHandler:
decorators_classes = [ decorators_classes = [
DimensionDecorator, DimensionDecorator,
@@ -1241,6 +1325,7 @@ class DecorationsHandler:
StairDecorator, StairDecorator,
BreakDecorator, BreakDecorator,
SectionViewDecorator, SectionViewDecorator,
TextDecorator,
] ]
installed = None installed = None
@@ -32,6 +32,7 @@ import ifcopenshell.util.selector
import ifcopenshell.util.representation import ifcopenshell.util.representation
import blenderbim.bim.schema import blenderbim.bim.schema
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.svgwriter as svgwriter import blenderbim.bim.module.drawing.svgwriter as svgwriter
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.sheeter as sheeter
@@ -57,6 +58,13 @@ def open_with_user_command(user_command, path):
webbrowser.open("file://" + 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): class AddDrawing(bpy.types.Operator):
bl_idname = "bim.add_drawing" bl_idname = "bim.add_drawing"
bl_label = "Add Drawing" bl_label = "Add Drawing"
@@ -79,7 +87,7 @@ class AddDrawing(bpy.types.Operator):
view_collection = bpy.data.collections.new("IfcGroup/" + new.name) view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
views_collection.children.link(view_collection) views_collection.children.link(view_collection)
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name)) 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.type = "ORTHO"
camera.data.ortho_scale = 50 # The default of 6m is too small camera.data.ortho_scale = 50 # The default of 6m is too small
camera.data.clip_end = 10 # A slightly more reasonable default camera.data.clip_end = 10 # A slightly more reasonable default
@@ -573,28 +581,45 @@ class AddAnnotation(bpy.types.Operator):
) )
if not subcontext: if not subcontext:
return {"FINISHED"} return {"FINISHED"}
if self.data_type == "text": # TODO: reimplement bulk smart tagging
if context.selected_objects: # if self.data_type == "text":
for selected_object in context.selected_objects: # if context.selected_objects:
obj = annotation.Annotator.add_text(context, related_element=selected_object) # for selected_object in context.selected_objects:
else: # obj = annotation.Annotator.add_text(context, related_element=selected_object)
obj = annotation.Annotator.add_text(context) # else:
else: # obj = annotation.Annotator.add_text(context)
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context) # else:
if self.obj_name == "Break": obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context)
obj = annotation.Annotator.add_plane_to_annotation(obj, context) if self.object_type == "BREAKLINE":
else: obj = annotation.Annotator.add_plane_to_annotation(obj, context)
obj = annotation.Annotator.add_line_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: 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: else:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj context.view_layer.objects.active = obj
obj.select_set(True) obj.select_set(True)
bpy.ops.object.mode_set(mode="EDIT") if obj.data:
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"} return {"FINISHED"}
@@ -888,29 +913,6 @@ class RemoveVariable(bpy.types.Operator):
return {"FINISHED"} 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): class RemoveDrawing(bpy.types.Operator):
bl_idname = "bim.remove_drawing" bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing" bl_label = "Remove Drawing"
@@ -1380,3 +1382,30 @@ class AddSectionsAnnotations(bpy.types.Operator):
view_coll.objects.link(obj) view_coll.objects.link(obj)
return {"FINISHED"} 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)
@@ -169,6 +169,10 @@ def refreshTitleblocks(self, context):
def toggleDecorations(self, context): def toggleDecorations(self, context):
toggle = self.should_draw_decorations toggle = self.should_draw_decorations
if toggle: 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) decoration.DecorationsHandler.install(context)
else: else:
decoration.DecorationsHandler.uninstall() decoration.DecorationsHandler.uninstall()
@@ -353,6 +357,9 @@ class BIMCameraProperties(PropertyGroup):
class BIMTextProperties(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( font_size: EnumProperty(
items=[ items=[
("1.8", "1.8 - Small", ""), ("1.8", "1.8 - Small", ""),
@@ -361,6 +368,7 @@ class BIMTextProperties(PropertyGroup):
("5.0", "5.0 - Header", ""), ("5.0", "5.0 - Header", ""),
("7.0", "7.0 - Title", ""), ("7.0", "7.0 - Title", ""),
], ],
default="2.5",
update=refreshFontSize, update=refreshFontSize,
name="Font Size", name="Font Size",
) )
@@ -24,6 +24,7 @@ import pystache
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import svgwrite import svgwrite
import ifcopenshell import ifcopenshell
import ifcopenshell.util.representation
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
@@ -208,8 +209,7 @@ class SvgWriter:
for obj_data in self.annotations.get("solid_objs", []): for obj_data in self.annotations.get("solid_objs", []):
self.draw_line_annotation(obj_data, ["solid"]) self.draw_line_annotation(obj_data, ["solid"])
if self.annotations.get("leader_obj"): self.draw_leader_annotations()
self.draw_line_annotation(self.annotations["leader_obj"], ["leader"])
if self.annotations.get("plan_level_obj"): if self.annotations.get("plan_level_obj"):
matrix_world = self.annotations["plan_level_obj"].matrix_world 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)) 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): 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 x_offset = self.raw_width / 2
y_offset = self.raw_height / 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(position)
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 = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0)) 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) projected_x_axis = self.project_point_onto_camera(position + local_x_axis)
angle = math.degrees( angle = math.degrees(
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed( (Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
Vector((1, 0)) 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( if text_literal.BoxAlignment == "top-left":
angle, alignment_baseline = "hanging"
(text_position * self.scale)[0], text_anchor = "start"
(text_position * self.scale)[1], 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): def draw_break_annotations(self, break_obj):
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
@@ -17,7 +17,10 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel from bpy.types import Panel
from blenderbim.bim.module.drawing.data import TextData
class BIM_PT_camera(Panel): class BIM_PT_camera(Panel):
@@ -233,39 +236,57 @@ class BIM_PT_sheets(Panel):
class BIM_PT_text(Panel): class BIM_PT_text(Panel):
bl_label = "Text Paper Space" bl_label = "IFC Text"
bl_idname = "BIM_PT_text" bl_idname = "BIM_PT_text"
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "data" bl_context = "object"
@classmethod @classmethod
def poll(cls, context): 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): def draw(self, context):
layout = self.layout if not TextData.is_loaded:
layout.use_property_split = True TextData.load()
props = context.active_object.data.BIMTextProperties
row = layout.row() self.layout.use_property_split = True
row.operator("bim.propagate_text_data") 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.prop(props, "font_size")
row = layout.row() row = self.layout.row()
row.prop(props, "symbol") row.prop(props, "symbol")
row = layout.row() row = self.layout.row()
row.prop(props, "related_element") row.prop(props, "related_element")
row = layout.row() row = self.layout.row()
row.operator("bim.add_variable") row.operator("bim.add_variable")
for index, variable in enumerate(props.variables): for index, variable in enumerate(props.variables):
row = layout.row(align=True) row = self.layout.row(align=True)
row.prop(variable, "name") row.prop(variable, "name")
row.operator("bim.remove_variable", icon="X", text="").index = index row.operator("bim.remove_variable", icon="X", text="").index = index
row = layout.row() row = self.layout.row()
row.prop(variable, "prop_key") row.prop(variable, "prop_key")
@@ -185,6 +185,7 @@ class UpdateRepresentation(bpy.types.Operator):
product, old_representation product, old_representation
) )
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product) 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) new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
+36
View File
@@ -0,0 +1,36 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
+10
View File
@@ -70,6 +70,16 @@ class Context:
def set_context(cls, context): pass 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 @interface
class Geometry: class Geometry:
def change_object_data(cls, obj, data, is_global=False): pass def change_object_data(cls, obj, data, is_global=False): pass
@@ -21,6 +21,7 @@ from blenderbim.tool.blender import Blender
from blenderbim.tool.brick import Brick from blenderbim.tool.brick import Brick
from blenderbim.tool.collector import Collector from blenderbim.tool.collector import Collector
from blenderbim.tool.context import Context from blenderbim.tool.context import Context
from blenderbim.tool.drawing import Drawing
from blenderbim.tool.geometry import Geometry from blenderbim.tool.geometry import Geometry
from blenderbim.tool.ifc import Ifc from blenderbim.tool.ifc import Ifc
from blenderbim.tool.material import Material from blenderbim.tool.material import Material
+61
View File
@@ -0,0 +1,61 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -105,6 +105,12 @@ class Geometry(blenderbim.core.tool.Geometry):
@classmethod @classmethod
def get_ifc_representation_class(cls, element, representation): 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) material = ifcopenshell.util.element.get_material(element)
if material and material.is_a("IfcMaterialProfileSetUsage"): if material and material.is_a("IfcMaterialProfileSetUsage"):
return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
@@ -151,6 +157,12 @@ class Geometry(blenderbim.core.tool.Geometry):
def get_styles(cls, obj): def get_styles(cls, obj):
return [tool.Style.get_style(s.material) for s in obj.material_slots if s.material] 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 @classmethod
def get_total_representation_items(cls, obj): def get_total_representation_items(cls, obj):
return max(1, len(obj.material_slots)) return max(1, len(obj.material_slots))
+1
View File
@@ -5,6 +5,7 @@ markers =
bimtester bimtester
brick brick
context context
drawing
geometry geometry
material material
misc misc
+7
View File
@@ -63,6 +63,13 @@ def context():
prophet.verify() prophet.verify()
@pytest.fixture
def drawing():
prophet = Prophecy(blenderbim.core.tool.Drawing)
yield prophet
prophet.verify()
@pytest.fixture @pytest.fixture
def geometry(): def geometry():
prophet = Prophecy(blenderbim.core.tool.Geometry) prophet = Prophecy(blenderbim.core.tool.Geometry)
+43
View File
@@ -0,0 +1,43 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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")
+96
View File
@@ -0,0 +1,96 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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"
+24 -1
View File
@@ -178,7 +178,16 @@ class TestGetStyles(NewFile):
tool.Ifc.link(style, material) tool.Ifc.link(style, material)
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
obj.data.materials.append(material) 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): class TestGetCartesianPointCoordinateOffset(NewFile):
@@ -490,6 +499,20 @@ class TestGetIfcRepresentationClass(NewFile):
subject.get_ifc_representation_class(element, representation) == "IfcExtrudedAreaSolid/IfcCircleProfileDef" 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): def test_returning_null_for_non_parametric_representations(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
+29 -29
View File
@@ -18,18 +18,18 @@
import bpy import bpy
import ifcopenshell import ifcopenshell
import test.bim.bootstrap
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
from blenderbim.tool.owner import Owner as subject from blenderbim.tool.owner import Owner as subject
class TestImplementsTool(test.bim.bootstrap.NewFile): class TestImplementsTool(NewFile):
def test_run(self): def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Owner) assert isinstance(subject(), blenderbim.core.tool.Owner)
class TestSetUser(test.bim.bootstrap.NewFile): class TestSetUser(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
@@ -38,7 +38,7 @@ class TestSetUser(test.bim.bootstrap.NewFile):
assert bpy.context.scene.BIMOwnerProperties.active_user_id == user.id() assert bpy.context.scene.BIMOwnerProperties.active_user_id == user.id()
class TestGetUser(test.bim.bootstrap.NewFile): class TestGetUser(NewFile):
def test_run(self): def test_run(self):
assert subject.get_user() is None assert subject.get_user() is None
TestSetUser().test_run() TestSetUser().test_run()
@@ -56,14 +56,14 @@ class TestGetUser(test.bim.bootstrap.NewFile):
assert subject.get_user() == user2 assert subject.get_user() == user2
class TestClearUser(test.bim.bootstrap.NewFile): class TestClearUser(NewFile):
def test_run(self): def test_run(self):
TestSetUser().test_run() TestSetUser().test_run()
subject.clear_user() subject.clear_user()
assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0 assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0
class TestSetAddress(test.bim.bootstrap.NewFile): class TestSetAddress(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
address = ifc.createIfcPostalAddress() address = ifc.createIfcPostalAddress()
@@ -71,7 +71,7 @@ class TestSetAddress(test.bim.bootstrap.NewFile):
assert bpy.context.scene.BIMOwnerProperties.active_address_id == address.id() 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): def test_importing_a_postal_address(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -152,7 +152,7 @@ class TestImportAddressAttributes(test.bim.bootstrap.NewFile):
assert len(props.messaging_ids) == 0 assert len(props.messaging_ids) == 0
class TestClearAddress(test.bim.bootstrap.NewFile): class TestClearAddress(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
address = ifc.createIfcPostalAddress() address = ifc.createIfcPostalAddress()
@@ -161,7 +161,7 @@ class TestClearAddress(test.bim.bootstrap.NewFile):
assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0 assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0
class TestGetAddress(test.bim.bootstrap.NewFile): class TestGetAddress(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -170,7 +170,7 @@ class TestGetAddress(test.bim.bootstrap.NewFile):
assert subject().get_address() == address assert subject().get_address() == address
class TestExportAttributes(test.bim.bootstrap.NewFile): class TestExportAttributes(NewFile):
def test_exporting_a_postal_address(self): def test_exporting_a_postal_address(self):
TestImportAddressAttributes().test_importing_a_postal_address() TestImportAddressAttributes().test_importing_a_postal_address()
assert subject().export_address_attributes() == { 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): def test_run(self):
subject().add_address_attribute("AddressLines") subject().add_address_attribute("AddressLines")
subject().add_address_attribute("TelephoneNumbers") subject().add_address_attribute("TelephoneNumbers")
@@ -216,7 +216,7 @@ class TestAddAddressAttribute(test.bim.bootstrap.NewFile):
assert len(props.messaging_ids) == 1 assert len(props.messaging_ids) == 1
class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile): class TestRemoveAddressAttribute(NewFile):
TestAddAddressAttribute().test_run() TestAddAddressAttribute().test_run()
subject().remove_address_attribute("AddressLines", 0) subject().remove_address_attribute("AddressLines", 0)
subject().remove_address_attribute("TelephoneNumbers", 0) subject().remove_address_attribute("TelephoneNumbers", 0)
@@ -231,14 +231,14 @@ class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile):
assert len(props.messaging_ids) == 0 assert len(props.messaging_ids) == 0
class TestSetOrganisation(test.bim.bootstrap.NewFile): class TestSetOrganisation(NewFile):
def test_run(self): def test_run(self):
organisation = ifcopenshell.file().createIfcOrganization() organisation = ifcopenshell.file().createIfcOrganization()
subject().set_organisation(organisation) subject().set_organisation(organisation)
assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id() assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id()
class TestImportOrganisationAttributes(test.bim.bootstrap.NewFile): class TestImportOrganisationAttributes(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -269,7 +269,7 @@ class TestImportOrganisationAttributes(test.bim.bootstrap.NewFile):
assert props.organisation_attributes.get("Description").string_value == "" assert props.organisation_attributes.get("Description").string_value == ""
class TestClearOrganisation(test.bim.bootstrap.NewFile): class TestClearOrganisation(NewFile):
def test_run(self): def test_run(self):
props = bpy.context.scene.BIMOwnerProperties props = bpy.context.scene.BIMOwnerProperties
props.active_organisation_id = 1 props.active_organisation_id = 1
@@ -277,7 +277,7 @@ class TestClearOrganisation(test.bim.bootstrap.NewFile):
assert props.active_organisation_id == 0 assert props.active_organisation_id == 0
class TestExportOrganisationAttributes(test.bim.bootstrap.NewFile): class TestExportOrganisationAttributes(NewFile):
def test_run(self): def test_run(self):
TestImportOrganisationAttributes().test_run() TestImportOrganisationAttributes().test_run()
assert subject().export_organisation_attributes() == { 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): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -296,14 +296,14 @@ class TestGetOrganisation(test.bim.bootstrap.NewFile):
assert subject().get_organisation() == organisation assert subject().get_organisation() == organisation
class TestSetPerson(test.bim.bootstrap.NewFile): class TestSetPerson(NewFile):
def test_run(self): def test_run(self):
person = ifcopenshell.file().createIfcPerson() person = ifcopenshell.file().createIfcPerson()
subject().set_person(person) subject().set_person(person)
assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id()
class TestImportPersonAttributes(test.bim.bootstrap.NewFile): class TestImportPersonAttributes(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -346,7 +346,7 @@ class TestImportPersonAttributes(test.bim.bootstrap.NewFile):
assert props.person_attributes.get("GivenName").string_value == "" assert props.person_attributes.get("GivenName").string_value == ""
class TestClearPerson(test.bim.bootstrap.NewFile): class TestClearPerson(NewFile):
def test_run(self): def test_run(self):
props = bpy.context.scene.BIMOwnerProperties props = bpy.context.scene.BIMOwnerProperties
props.active_person_id = 1 props.active_person_id = 1
@@ -354,7 +354,7 @@ class TestClearPerson(test.bim.bootstrap.NewFile):
assert props.active_person_id == 0 assert props.active_person_id == 0
class TestExportPersonAttributes(test.bim.bootstrap.NewFile): class TestExportPersonAttributes(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -385,7 +385,7 @@ class TestExportPersonAttributes(test.bim.bootstrap.NewFile):
assert result["SuffixTitles"] is None assert result["SuffixTitles"] is None
class TestGetPerson(test.bim.bootstrap.NewFile): class TestGetPerson(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -394,7 +394,7 @@ class TestGetPerson(test.bim.bootstrap.NewFile):
assert subject().get_person() == person assert subject().get_person() == person
class TestAddPersonAttribute(test.bim.bootstrap.NewFile): class TestAddPersonAttribute(NewFile):
def test_run(self): def test_run(self):
subject().add_person_attribute("MiddleNames") subject().add_person_attribute("MiddleNames")
subject().add_person_attribute("PrefixTitles") subject().add_person_attribute("PrefixTitles")
@@ -405,7 +405,7 @@ class TestAddPersonAttribute(test.bim.bootstrap.NewFile):
assert len(props.suffix_titles) == 1 assert len(props.suffix_titles) == 1
class TestRemovePersonAttribute(test.bim.bootstrap.NewFile): class TestRemovePersonAttribute(NewFile):
def test_run(self): def test_run(self):
subject().add_person_attribute("MiddleNames") subject().add_person_attribute("MiddleNames")
subject().remove_person_attribute("MiddleNames", 0) subject().remove_person_attribute("MiddleNames", 0)
@@ -419,14 +419,14 @@ class TestRemovePersonAttribute(test.bim.bootstrap.NewFile):
assert len(props.suffix_titles) == 0 assert len(props.suffix_titles) == 0
class TestSetRole(test.bim.bootstrap.NewFile): class TestSetRole(NewFile):
def test_run(self): def test_run(self):
role = ifcopenshell.file().createIfcActorRole() role = ifcopenshell.file().createIfcActorRole()
subject().set_role(role) subject().set_role(role)
assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id() assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id()
class TestImportRoleAttributes(test.bim.bootstrap.NewFile): class TestImportRoleAttributes(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -454,7 +454,7 @@ class TestImportRoleAttributes(test.bim.bootstrap.NewFile):
assert props.role_attributes.get("Role").enum_value == "ARCHITECT" assert props.role_attributes.get("Role").enum_value == "ARCHITECT"
class TestClearRole(test.bim.bootstrap.NewFile): class TestClearRole(NewFile):
def test_run(self): def test_run(self):
role = ifcopenshell.file().createIfcActorRole() role = ifcopenshell.file().createIfcActorRole()
subject().set_role(role) subject().set_role(role)
@@ -462,7 +462,7 @@ class TestClearRole(test.bim.bootstrap.NewFile):
assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0 assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0
class TestGetRole(test.bim.bootstrap.NewFile): class TestGetRole(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -471,7 +471,7 @@ class TestGetRole(test.bim.bootstrap.NewFile):
assert subject().get_role() == role assert subject().get_role() == role
class TestExportRoleAttributes(test.bim.bootstrap.NewFile): class TestExportRoleAttributes(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
@@ -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)
@@ -28,8 +28,11 @@ class Usecase:
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef # IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids # IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
# IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage # IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
# IfcGeometricCurveSet/IfcTextLiteral
# IfcTextLiteral
"ifc_representation_class": None, # Whether to cast a mesh into a particular class "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 "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 = [] self.ifc_vertices = []
for key, value in settings.items(): for key, value in settings.items():
@@ -117,9 +120,16 @@ class Usecase:
return self.create_lighting_representation() return self.create_lighting_representation()
def create_plan_representation(self): def create_plan_representation(self):
if self.settings["context"].ContextIdentifier == "Annotation": if self.settings["ifc_representation_class"] == "IfcTextLiteral":
if isinstance(self.settings["geometry"], bpy.types.TextCurve): return self.create_text_representation()
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 = self.create_geometric_curve_set_representation(is_2d=True)
shape_representation.RepresentationType = "Annotation2D" shape_representation.RepresentationType = "Annotation2D"
return shape_representation return shape_representation
@@ -143,8 +153,6 @@ class Usecase:
elif self.settings["context"].ContextIdentifier == "SurveyPoints": elif self.settings["context"].ContextIdentifier == "SurveyPoints":
pass pass
else: 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 = self.create_geometric_curve_set_representation(is_2d=True)
shape_representation.RepresentationType = "Annotation2D" shape_representation.RepresentationType = "Annotation2D"
return shape_representation return shape_representation
@@ -180,21 +188,8 @@ class Usecase:
) )
def create_text(self): def create_text(self):
text = self.settings["geometry"] if self.settings["text_literal"]:
if text.align_y in ["TOP_BASELINE", "BOTTOM_BASELINE", "BOTTOM"]: return self.settings["text_literal"]
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( origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)), self.file.createIfcDirection((0.0, 0.0, 1.0)),
@@ -203,7 +198,7 @@ class Usecase:
# TODO: Planar extent right now is wrong ... # TODO: Planar extent right now is wrong ...
return self.file.createIfcTextLiteralWithExtent( 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): def create_variable_representation(self):
@@ -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"