Add section cut multiprocessing, grids, hidden lines, stair arrows, text annotations, and leaders

This commit is contained in:
Dion Moult
2019-12-13 17:25:52 +11:00
parent 4f7925580a
commit c0cd0ce07e
5 changed files with 370 additions and 186 deletions
@@ -11,6 +11,10 @@ bl_info = {
"category": "Import-Export" "category": "Import-Export"
} }
# are we running inside Blender?
import sys
bpy = sys.modules.get('bpy')
if bpy is not None:
import bpy import bpy
from . import ui, prop, operator from . import ui, prop, operator
+189 -41
View File
@@ -5,6 +5,8 @@ import numpy
import pickle import pickle
import sys import sys
from pathlib import Path from pathlib import Path
mathutils = sys.modules.get('mathutils')
if mathutils is not None:
from mathutils import Vector from mathutils import Vector
from math import degrees from math import degrees
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -42,6 +44,80 @@ from OCC.TopoDS import topods
import ifcopenshell import ifcopenshell
import ifcopenshell.geom import ifcopenshell.geom
cwd = os.path.dirname(os.path.realpath(__file__))
this_file = os.path.join(cwd, 'cut_ifc.py')
def get_booleaned_edges(shape):
edges = []
exp = OCC.TopExp.TopExp_Explorer(shape, OCC.TopAbs.TopAbs_EDGE)
while exp.More():
edges.append(topods.Edge(exp.Current()))
exp.Next()
return edges
def connect_edges_into_wires(unconnected_edges):
edges = OCC.TopTools.TopTools_HSequenceOfShape()
edges_handle = OCC.TopTools.Handle_TopTools_HSequenceOfShape(edges)
wires = OCC.TopTools.TopTools_HSequenceOfShape()
wires_handle = OCC.TopTools.Handle_TopTools_HSequenceOfShape(wires)
for edge in unconnected_edges:
edges.Append(edge)
OCC.ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle)
return wires_handle.GetObject()
def do_cut(process_data):
global_id, shape, section, trsf_data = process_data
axis = OCC.gp.gp_Ax2(
OCC.gp.gp_Pnt(
trsf_data['top_left_corner'][0],
trsf_data['top_left_corner'][1],
trsf_data['top_left_corner'][2]),
OCC.gp.gp_Dir(
trsf_data['projection'][0],
trsf_data['projection'][1],
trsf_data['projection'][2]),
OCC.gp.gp_Dir(
trsf_data['x_axis'][0],
trsf_data['x_axis'][1],
trsf_data['x_axis'][2])
)
source = OCC.gp.gp_Ax3(axis)
destination = OCC.gp.gp_Ax3(
OCC.gp.gp_Pnt(0, 0, 0),
OCC.gp.gp_Dir(0, 0, -1),
OCC.gp.gp_Dir(1, 0, 0))
transformation = OCC.gp.gp_Trsf()
transformation.SetDisplacement(source, destination)
cut_polygons = []
section = OCC.BRepAlgoAPI.BRepAlgoAPI_Section(section, shape).Shape()
section_edges = get_booleaned_edges(section)
if len(section_edges) <= 0:
return cut_polygons
wires = connect_edges_into_wires(section_edges)
for i in range(wires.Length()):
wire_shape = wires.Value(i+1)
transformed_wire = OCC.BRepBuilderAPI.BRepBuilderAPI_Transform(
wire_shape, transformation)
wire_shape = transformed_wire.Shape()
wire = topods.Wire(wire_shape)
face = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeFace(wire).Face()
points = []
exp = OCC.BRepTools.BRepTools_WireExplorer(wire)
while exp.More():
point = OCC.BRep.BRep_Tool.Pnt(exp.CurrentVertex())
points.append((point.X(), -point.Y()))
exp.Next()
cut_polygons.append({ 'global_id': global_id, 'points': points })
return cut_polygons
class IfcCutter: class IfcCutter:
def __init__(self): def __init__(self):
self.product_shapes = [] self.product_shapes = []
@@ -253,10 +329,16 @@ class IfcCutter:
self.section_box['face'] = section_box.BottomFace() self.section_box['face'] = section_box.BottomFace()
source = OCC.gp.gp_Ax3(axis) source = OCC.gp.gp_Ax3(axis)
self.transformation_data = {
'top_left_corner': self.section_box['top_left_corner'],
'projection': self.section_box['projection'],
'x_axis': self.section_box['x_axis']
}
destination = OCC.gp.gp_Ax3( destination = OCC.gp.gp_Ax3(
OCC.gp.gp_Pnt(0, 0, 0), OCC.gp.gp_Pnt(0, 0, 0),
OCC.gp.gp_Dir(0, 0, -1), OCC.gp.gp_Dir(0, 0, -1),
OCC.gp.gp_Dir(1, 0, 0)) OCC.gp.gp_Dir(1, 0, 0))
self.transformation_dest = destination
self.transformation = OCC.gp.gp_Trsf() self.transformation = OCC.gp.gp_Trsf()
self.transformation.SetDisplacement(source, destination) self.transformation.SetDisplacement(source, destination)
@@ -489,51 +571,22 @@ class IfcCutter:
return return
total_product_shapes = len(self.product_shapes) total_product_shapes = len(self.product_shapes)
n = 0 process_data = [(p.GlobalId, s, self.section_box['face'], self.transformation_data) for p, s in self.product_shapes]
for product, shape in self.product_shapes:
print('{}/{} cut elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
n += 1
section = OCC.BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box['face'], shape).Shape()
section_edges = self.get_booleaned_edges(section)
if len(section_edges) <= 0:
continue
wires = self.connect_edges_into_wires(section_edges)
for i in range(wires.Length()):
wire_shape = wires.Value(i+1)
transformed_wire = OCC.BRepBuilderAPI.BRepBuilderAPI_Transform( import multiprocessing
wire_shape, self.transformation) import bpy
wire_shape = transformed_wire.Shape() multiprocessing.set_executable(bpy.app.binary_path_python)
wire = topods.Wire(wire_shape) with multiprocessing.Pool(9) as p:
face = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeFace(wire).Face() results = p.map(do_cut, process_data)
for result in results:
points = [] polygons = [p for p in result if p['points']]
exp = OCC.BRepTools.BRepTools_WireExplorer(wire) self.cut_polygons.extend(polygons)
while exp.More():
point = OCC.BRep.BRep_Tool.Pnt(exp.CurrentVertex())
points.append((point.X(), -point.Y()))
exp.Next()
self.cut_polygons.append({
'global_id': product.GlobalId,
'points': points
})
if not os.path.isfile(self.cut_pickle_file): if not os.path.isfile(self.cut_pickle_file):
with open(self.cut_pickle_file, 'wb') as pickle_file: with open(self.cut_pickle_file, 'wb') as pickle_file:
pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL) pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
def connect_edges_into_wires(self, unconnected_edges):
edges = OCC.TopTools.TopTools_HSequenceOfShape()
edges_handle = OCC.TopTools.Handle_TopTools_HSequenceOfShape(edges)
wires = OCC.TopTools.TopTools_HSequenceOfShape()
wires_handle = OCC.TopTools.Handle_TopTools_HSequenceOfShape(wires)
for edge in unconnected_edges:
edges.Append(edge)
OCC.ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle)
return wires_handle.GetObject()
class IfcCutterDebug(IfcCutter): class IfcCutterDebug(IfcCutter):
def cut(self): def cut(self):
@@ -672,10 +725,11 @@ class SvgWriter():
def draw_annotations(self): def draw_annotations(self):
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
for edge in self.ifc_cutter.annotation_obj.data.edges: if self.ifc_cutter.dimension_obj:
for edge in self.ifc_cutter.dimension_obj.data.edges:
classes = ['annotation', 'dimension'] classes = ['annotation', 'dimension']
v0 = self.ifc_cutter.annotation_obj.data.vertices[edge.vertices[0]].co v0 = self.ifc_cutter.dimension_obj.data.vertices[edge.vertices[0]].co
v1 = self.ifc_cutter.annotation_obj.data.vertices[edge.vertices[1]].co v1 = self.ifc_cutter.dimension_obj.data.vertices[edge.vertices[1]].co
start = Vector(((x_offset + v0.x), (y_offset - v0.y))) start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y))) end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
mid = ((end - start) / 2) + start mid = ((end - start) / 2) + start
@@ -705,6 +759,100 @@ class SvgWriter():
'text-anchor': 'middle' 'text-anchor': 'middle'
})) }))
for grid_obj in self.ifc_cutter.grid_objs:
for edge in grid_obj.data.edges:
classes = ['annotation', 'grid']
v0 = grid_obj.data.vertices[edge.vertices[0]].co
v1 = grid_obj.data.vertices[edge.vertices[1]].co
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.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#grid-marker)'
line['marker-end'] = 'url(#grid-marker)'
line['stroke-dasharray'] = '12.5, 3, 3, 3'
self.svg.add(self.svg.text(grid_obj.name.split('/')[1], insert=tuple(start * self.scale), **{
'font-size': '8.25', # 5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
self.svg.add(self.svg.text(grid_obj.name.split('/')[1], insert=tuple(end * self.scale), **{
'font-size': '8.25', # 5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
if self.ifc_cutter.hidden_obj:
for edge in self.ifc_cutter.hidden_obj.data.edges:
classes = ['annotation', 'hidden']
v0 = self.ifc_cutter.hidden_obj.data.vertices[edge.vertices[0]].co
v1 = self.ifc_cutter.hidden_obj.data.vertices[edge.vertices[1]].co
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.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['stroke-dasharray'] = '3, 2'
if self.ifc_cutter.leader_obj:
for spline in self.ifc_cutter.leader_obj.data.splines:
classes = ['annotation', 'leader']
d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in spline.points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-end'] = 'url(#leader-marker)'
if self.ifc_cutter.stair_obj:
for spline in self.ifc_cutter.stair_obj.data.splines:
classes = ['annotation', 'stair']
d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in spline.points])
d = 'M{}'.format(d[1:])
start = Vector(((x_offset + spline.points[0].co.x), (y_offset - spline.points[0].co.y)))
next_point = Vector(((x_offset + spline.points[1].co.x), (y_offset - spline.points[1].co.y)))
text_position = (start * self.scale) - ((next_point - start).normalized() * 5)
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-start'] = 'url(#stair-marker-start)'
path['marker-end'] = 'url(#stair-marker-end)'
self.svg.add(self.svg.text('UP', insert=tuple(text_position), **{
'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
for text_obj in self.ifc_cutter.text_objs:
loc, rot, scale = self.ifc_cutter.camera_obj.matrix_world.decompose()
pos = (text_obj.location - self.ifc_cutter.camera_obj.location) @ rot.to_matrix()
text_position = Vector(((x_offset + pos.x), (y_offset - pos.y)))
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'
self.svg.add(self.svg.text(text_obj.data.body, insert=tuple(text_position * self.scale), **{
'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT',
'text-anchor': text_anchor,
'alignment-baseline': alignment_baseline,
'dominant-baseline': alignment_baseline
}))
def draw_cut_polygons(self): def draw_cut_polygons(self):
for polygon in self.ifc_cutter.cut_polygons: for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, 'cut') self.draw_polygon(polygon, 'cut')
@@ -1,3 +1,5 @@
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; } .cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation.leader { fill: none; }
.annotation.stair { fill: none; }
@@ -1,8 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?> <?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"> <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="dimension-marker" markerHeight="20" markerWidth="20" refX="10" refY="15" orient="auto"> <marker id="dimension-marker" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" /> <path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
<path d="M 10 0 L 10 30" class="annotation" style="stroke-width:1;" /> <path d="M 10 0 L 10 30" class="annotation" style="stroke-width:1;" />
<path d="M 5 20 L 15 10" class="annotation" style="stroke-width:3;" /> <path d="M 5 20 L 15 10" class="annotation" style="stroke-width:3;" />
</marker> </marker>
<marker id="grid-marker" markerHeight="35" markerWidth="35" orient="auto" refX="17.5" refY="17.5">
<circle cx="17.5" cy="17.5" fill="white" stroke="black" r="15" style="stroke-width: 2"/>
</marker>
<marker id="leader-marker" markerHeight="7" markerWidth="10" orient="auto" refX="10" refY="3.5">
<path d="M 0 0 L 0 7 L 10 3.5" class="annotation" style="fill:black;" />
</marker>
<marker id="stair-marker-start" markerHeight="10" markerWidth="10" orient="auto" refX="5" refY="5">
<circle cx="5" cy="5" fill="black" stroke="none" r="5"/>
</marker>
<marker id="stair-marker-end" markerHeight="42" markerWidth="21" orient="auto" refX="21" refY="21">
<path d="M 0 0 L 21 21 L 0 42" class="annotation stair" style="stroke-width:2" />
</marker>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 1.4 KiB

+21 -3
View File
@@ -788,9 +788,27 @@ class CutSection(bpy.types.Operator):
ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir
ifc_cutter.diagram_name = self.diagram_name ifc_cutter.diagram_name = self.diagram_name
ifc_cutter.background_image = bpy.context.scene.render.filepath ifc_cutter.background_image = bpy.context.scene.render.filepath
ifc_cutter.annotation_obj = bpy.data.objects['{}-Annotation'.format( ifc_cutter.leader_obj = None
self.diagram_name ifc_cutter.stair_obj = None
)] ifc_cutter.dimension_obj = None
ifc_cutter.hidden_obj = None
ifc_cutter.grid_objs = []
ifc_cutter.text_objs = []
for obj in bpy.context.selected_objects:
if 'Leader' in obj.name:
ifc_cutter.leader_obj = obj
elif 'Stair' in obj.name:
ifc_cutter.stair_obj = obj
elif 'Dimension' in obj.name:
ifc_cutter.dimension_obj = obj
elif 'Hidden' in obj.name:
ifc_cutter.hidden_obj = obj
elif 'IfcGrid' in obj.name:
ifc_cutter.grid_objs.append(obj)
elif obj.type == 'CAMERA':
ifc_cutter.camera_obj = obj
elif obj.type == 'FONT':
ifc_cutter.text_objs.append(obj)
ifc_cutter.section_box = { ifc_cutter.section_box = {
'projection': tuple(projection), 'projection': tuple(projection),
'x_axis': tuple(x_axis), 'x_axis': tuple(x_axis),