WIP support for cutting a section with dimension annotation lines

This commit is contained in:
Dion Moult
2019-12-10 18:54:26 +11:00
parent 1d04a3a5c7
commit 2eeb507b78
8 changed files with 167 additions and 18 deletions
@@ -61,8 +61,10 @@ classes = (
operator.FetchObjectPassport,
operator.AddSubcontext,
operator.RemoveSubcontext,
operator.CutSection,
prop.Subcontext,
prop.BIMProperties,
prop.DocProperties,
prop.BIMLibrary,
prop.MapConversion,
prop.TargetCRS,
@@ -75,6 +77,7 @@ classes = (
prop.BIMMaterialProperties,
prop.SweptSolid,
prop.BIMMeshProperties,
ui.BIM_PT_documentation,
ui.BIM_PT_bim,
ui.BIM_PT_context,
ui.BIM_PT_qa,
@@ -102,6 +105,7 @@ def register():
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary)
bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion)
bpy.types.Scene.TargetCRS = bpy.props.PointerProperty(type=prop.TargetCRS)
@@ -117,6 +121,7 @@ def unregister():
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del(bpy.types.Scene.BIMProperties)
del(bpy.types.Scene.DocProperties)
del(bpy.types.Scene.MapConversion)
del(bpy.types.Scene.TargetCRS)
del(bpy.types.Object.BIMObjectProperties)
+76 -18
View File
@@ -1,11 +1,19 @@
import os
import math
import svgwrite
import time
import numpy
import pickle
import sys
from pathlib import Path
from mathutils import Vector
import xml.etree.ElementTree as ET
# This hack is required to get the svgwrite dependency to load
# TODO: decouple cutter into its own binary
cwd = os.path.dirname(os.path.realpath(__file__)) + os.path.sep
sys.path.append(cwd)
import svgwrite
import OCC.gp
import OCC.Geom
import OCC.Bnd
@@ -43,11 +51,14 @@ class IfcCutter:
self.product_shapes = []
self.background_elements = []
self.cut_polygons = []
self.data_dir = ''
self.ifc_files = []
self.unit = None
self.resolved_pixels = set()
self.should_get_background = False
self.pickle_file = 'shapes.pickle'
self.diagram_name = None
self.background_image = None
self.section_box = {
'projection': (0, 1, 0),
'x_axis': (1, 0, 0),
@@ -103,7 +114,7 @@ class IfcCutter:
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
def load_ifc_files(self):
for filename in Path('ifc/').glob('*.ifc'):
for filename in Path(self.data_dir).glob('*.ifc'):
print('Loading file {} ...'.format(filename))
self.ifc_files.append(ifcopenshell.open(filename))
@@ -558,6 +569,24 @@ class IfcCutterDebug(IfcCutter):
ifcopenshell.geom.utils.display_shape(element['geometry_face'])
input('Debug: showing background elements.')
class External(svgwrite.container.Group):
def __init__(self, xml, **extra):
self.xml = xml
# Remove namespace
ns = u'{http://www.w3.org/2000/svg}'
nsl = len(ns)
for elem in self.xml.getiterator():
if elem.tag.startswith(ns):
elem.tag = elem.tag[nsl:]
super(External, self).__init__(**extra)
def get_xml(self):
return self.xml
class SvgWriter():
def __init__(self, ifc_cutter):
self.ifc_cutter = ifc_cutter
@@ -565,27 +594,52 @@ class SvgWriter():
def write(self):
self.calculate_scale()
self.svg = svgwrite.Drawing('output.svg',
self.output = os.path.join(
self.ifc_cutter.data_dir,
'diagrams',
self.ifc_cutter.diagram_name + '.svg'
)
self.svg = svgwrite.Drawing(
self.output,
debug=False,
size=('{}mm'.format(self.ifc_cutter.section_box['x'] * self.scale),
'{}mm'.format(self.ifc_cutter.section_box['y'] * self.scale)),
viewBox=('0 0 {} {}'.format(
self.ifc_cutter.section_box['x'] * self.scale,
self.ifc_cutter.section_box['y'] * self.scale)))
size=('{}mm'.format(self.width), '{}mm'.format(self.height)),
viewBox=('0 0 {} {}'.format(self.width, self.height)))
self.add_stylesheet()
self.add_defs()
self.draw_background_image()
self.draw_background_elements()
self.draw_cut_polygons()
self.draw_annotations()
self.svg.save(pretty=True)
def calculate_scale(self):
# TODO: properly handle units
if self.ifc_cutter.unit.Name == 'METRE':
self.scale *= 1000
self.raw_width = self.ifc_cutter.section_box['x']
self.raw_height = self.ifc_cutter.section_box['y']
self.width = self.raw_width * self.scale
self.height = self.raw_height * self.scale
def add_stylesheet(self):
with open('styles/default.css', 'r') as stylesheet:
with open('{}styles/default.css'.format(self.ifc_cutter.data_dir), 'r') as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_defs(self):
tree = ET.parse('{}styles/defs.svg'.format(self.ifc_cutter.data_dir))
root = tree.getroot()
for child in root.getchildren():
self.svg.defs.add(External(child))
def draw_background_image(self):
self.svg.add(self.svg.image(
os.path.basename(self.ifc_cutter.background_image), **{
'width': self.width,
'height': self.height
}
))
def draw_background_elements(self):
for element in self.ifc_cutter.background_elements:
if element['type'] == 'polygon':
@@ -595,6 +649,19 @@ class SvgWriter():
elif element['type'] == 'line':
self.draw_line(element, 'background')
def draw_annotations(self):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for edge in self.ifc_cutter.annotation_obj.data.edges:
classes = ['annotation', 'dimension']
v0 = self.ifc_cutter.annotation_obj.data.vertices[edge.vertices[0]].co
v1 = self.ifc_cutter.annotation_obj.data.vertices[edge.vertices[1]].co
start = ((x_offset + v0.x) * self.scale, (y_offset - v0.y) * self.scale)
end = ((x_offset + v1.x) * self.scale, (y_offset - v1.y) * self.scale)
line = self.svg.add(self.svg.line(start=start, end=end, class_=' '.join(classes)))
line['marker-start'] = 'url(#dimension-marker)'
line['marker-end'] = 'url(#dimension-marker)'
def draw_cut_polygons(self):
for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, 'cut')
@@ -636,12 +703,3 @@ class SvgWriter():
classes.append('material-{}'.format(association.RelatingMaterial.Name))
classes.append('globalid-{}'.format(element.GlobalId))
return classes
print('# Starting process')
ifc_cutter = IfcCutter()
#ifc_cutter = IfcCutterDebug()
svg_writer = SvgWriter(ifc_cutter)
ifc_cutter.cut()
start_time = time.time()
svg_writer.write()
print('# SVG writing finished in {:.2f} seconds'.format(time.time() - start_time))
@@ -0,0 +1,3 @@
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.3px; }
.background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.05px; }
.annotation { stroke: black; stroke-linecap: 'round'; stroke-width: 0.05px; }
@@ -0,0 +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="dimension-marker" markerHeight="20" markerWidth="20" refX="7.5" refY="10" orient="auto">
<path d="M 7.5 0 L 7.5 20" class="annotation" style="stroke-width:2px;" />
<path d="M 0 0 L 15 20" class="annotation" style="stroke-width:3px;" />
</marker>
</svg>

After

Width:  |  Height:  |  Size: 498 B

@@ -6,8 +6,10 @@ import logging
from . import ifcopenshell
from . import export_ifc
from . import import_ifc
from . import cut_ifc
from bpy_extras.io_utils import ImportHelper
from itertools import cycle
from mathutils import Vector
class ExportIFC(bpy.types.Operator):
bl_idname = "export.ifc"
@@ -751,3 +753,59 @@ class RemoveSubcontext(bpy.types.Operator):
subcontext_index = int(subcontext_index)
getattr(bpy.context.scene.BIMProperties, '{}_subcontexts'.format(context)).remove(subcontext_index)
return {'FINISHED'}
class CutSection(bpy.types.Operator):
bl_idname = 'bim.cut_section'
bl_label = 'Cut Section'
def execute(self, context):
camera = bpy.context.active_object
if not (camera.type == 'CAMERA' and camera.data.type == 'ORTHO'):
return {'FINISHED'}
self.diagram_name = camera.name
bpy.context.scene.render.filepath = os.path.join(
bpy.context.scene.BIMProperties.data_dir,
'diagrams',
'{}.png'.format(self.diagram_name)
)
bpy.ops.render.render(write_still=True)
location = camera.location
render = bpy.context.scene.render
if self.is_landscape():
width = camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
depth = camera.data.clip_end
projection = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
x_axis = camera.matrix_world.to_quaternion() @ Vector((1, 0, 0))
y_axis = camera.matrix_world.to_quaternion() @ Vector((0, -1, 0))
top_left_corner = location - (width / 2 * x_axis) - (height / 2 * y_axis)
ifc_cutter = cut_ifc.IfcCutter()
ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir
ifc_cutter.diagram_name = self.diagram_name
ifc_cutter.background_image = bpy.context.scene.render.filepath
ifc_cutter.annotation_obj = bpy.data.objects['{}-Annotation'.format(
self.diagram_name
)]
ifc_cutter.section_box = {
'projection': tuple(projection),
'x_axis': tuple(x_axis),
'y_axis': tuple(y_axis),
'top_left_corner': tuple(top_left_corner),
'x': width,
'y': height,
'z': depth,
'shape': None,
'face': None
}
ifc_cutter.pickle_file = os.path.join(ifc_cutter.data_dir, 'shapes.pickle')
svg_writer = cut_ifc.SvgWriter(ifc_cutter)
ifc_cutter.cut()
svg_writer.write()
return {'FINISHED'}
def is_landscape(self):
return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y
+4
View File
@@ -171,6 +171,10 @@ class Subcontext(PropertyGroup):
target_view: StringProperty(name='Target View')
class DocProperties(PropertyGroup):
blah: StringProperty(name="Blah")
class BIMProperties(PropertyGroup):
schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory")
data_dir: StringProperty(default=os.path.join(cwd, 'data') + os.path.sep, name="Data Directory")
+14
View File
@@ -182,6 +182,20 @@ class BIM_PT_gis(Panel):
layout.row().prop(scene.TargetCRS, 'map_unit')
class BIM_PT_documentation(Panel):
bl_label = "Diagrammatic Documentation"
bl_idname = "BIM_PT_documentation"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'output'
def draw(self, context):
layout = self.layout
props = bpy.context.scene.DocProperties
row = layout.row()
row.operator('bim.cut_section')
class BIM_PT_context(Panel):
bl_label = "Geometric Representation Contexts"
bl_idname = "BIM_PT_context"