mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Rebuild text annotations to have leaders in the same object and use text literals. See #1153.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)])
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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")
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
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))
|
||||
|
||||
@@ -5,6 +5,7 @@ markers =
|
||||
bimtester
|
||||
brick
|
||||
context
|
||||
drawing
|
||||
geometry
|
||||
material
|
||||
misc
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/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):
|
||||
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user