Batting Annotation pattern

Added batting pattern to line annotations.
The way to use it is:
1) Add "Line" Annotation;
2) Change object type to "BATTING"
3) Add object property set "Pset_BBIM_Bating" (also added in this commit) to specify the batting thickness.

Currently there is no preview for batting in viewport.

Other changes:
- EPset_Annotation.ifc -> Psets_BBIM_Annotation.ifc (more general psets ifc library for the future use)
This commit is contained in:
Andrej730
2023-02-27 18:38:04 +05:00
parent 68bdbe4c4f
commit f5b6fdcb5b
8 changed files with 106 additions and 20 deletions
@@ -9,5 +9,8 @@ DATA;
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'Pset_BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -37,7 +37,6 @@ text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT';
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
.PredefinedType-FILLAREA { fill: white; stroke: black; }
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
</marker>
<!-- Tip: https://www.svgviewer.dev/ -->
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

@@ -137,8 +137,7 @@ class CreateDrawing(bpy.types.Operator):
Only available if :
- IFC file is created
- Camera is in Orthographic mode
"""
- Camera is in Orthographic mode"""
bl_idname = "bim.create_drawing"
bl_label = "Create Drawing"
@@ -31,7 +31,7 @@ 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
from math import pi
from math import pi, ceil
from mathutils import Vector
from mathutils import geometry
from blenderbim.bim.ifc import IfcStore
@@ -363,22 +363,95 @@ class SvgWriter:
self.draw_edge_annotation(obj, classes)
def draw_edge_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
for edge in obj.data.edges:
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
predefined_type = classes[2]
if predefined_type.endswith("-BATTING"):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
element = tool.Ifc.get_entity(obj)
pset_data = tool.Pset.get_element_pset_data(element, "Pset_BBIM_Batting") or {}
# SVG uses mm as default unit
conversion_to_mm = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) / 0.001
thickness = pset_data["Thickness"] * conversion_to_mm if "Thickness" in pset_data else 15.0
reverse_x = pset_data.get("Reverse pattern direction", False)
if reverse_x:
start_svg, end_svg = end_svg, start_svg
pattern_dir = (end_svg - start_svg).normalized()
pattern_length = (end_svg - start_svg).length
segment_width = thickness / 2.5
segments = ceil(pattern_length / segment_width)
end_marker_width = pattern_length - segment_width * (segments - 1)
points = [start_svg + pattern_dir * segment_width * i for i in range(segments)]
marker_id = f"batting-{element.GlobalId}"
marker_end_id = f"batting-end-{element.GlobalId}"
path_data = f"""M 0 {0.2*thickness}
A {0.5*segment_width} {0.2*thickness} 0 0 1 {segment_width} {0.2*thickness}
L {0.5*segment_width} {0.8*thickness}
M 0 {0.2*thickness}
L {0.5*segment_width} {0.8*thickness}
A {0.5*segment_width} {0.2*thickness} 0 0 0 {segment_width} {1.0*thickness}
M {0.5*segment_width} {0.8*thickness}
A {0.5*segment_width} {0.2*thickness} 0 0 1 0 {1.0*thickness}
"""
path_data = " ".join(path_data.split())
svg_path = self.svg.path(style="fill: none; stroke:black; stroke-width:0.18", d=path_data)
if reverse_x:
svg_path.update({"transform": f"scale(-1,-1) translate(-{segment_width}, -{thickness})"})
marker = self.svg.marker(
markerUnits="userSpaceOnUse",
insert=(0, thickness / 2),
size=(segment_width, thickness),
orient="auto",
id=marker_id,
)
marker.add(svg_path)
self.svg.add(marker)
# separate marker for the end of the pattern to truncate it more gracefully
marker_end = marker.copy()
marker_end.update({"markerWidth": end_marker_width, "id": marker_end_id})
self.svg.add(marker_end)
polyline_style = (
f"marker-start: url(#{marker_id}); "
f"marker-mid: url(#{marker_id}); stroke: none; "
f"marker-end: url(#{marker_end_id}); stroke: none; "
)
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
else:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
for edge in obj.data.edges:
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
)
def draw_leader_annotation(self, obj):
self.draw_line_annotation(obj)
+1
View File
@@ -486,6 +486,7 @@ class Profile:
@interface
class Pset:
def get_element_pset(cls, element, pset_name): pass
def get_element_pset_data(cls, element, pset_name): pass
@interface
+5
View File
@@ -29,3 +29,8 @@ class Pset(blenderbim.core.tool.Pset):
pset = psets.get(pset_name, None)
if pset:
return tool.Ifc.get().by_id(pset["id"])
@classmethod
def get_element_pset_data(cls, element, pset_name):
psets = ifcopenshell.util.element.get_psets(element)
return psets.get(pset_name, None)
+8
View File
@@ -707,9 +707,17 @@ def hit_undo():
bpy.ops.ed.undo_push(message="UNDO STEP")
bpy.ops.ed.undo()
# These definitions are not to be used in tests but simply in debugging failing tests
@given(parsers.parse("I run test code"))
@when(parsers.parse("I run test code"))
@then(parsers.parse("I run test code"))
def run_test_code():
pass
@given(parsers.parse("I save sample test files"))
@when(parsers.parse("I save sample test files"))
@then(parsers.parse("I save sample test files"))