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.FetchObjectPassport,
operator.AddSubcontext, operator.AddSubcontext,
operator.RemoveSubcontext, operator.RemoveSubcontext,
operator.CutSection,
prop.Subcontext, prop.Subcontext,
prop.BIMProperties, prop.BIMProperties,
prop.DocProperties,
prop.BIMLibrary, prop.BIMLibrary,
prop.MapConversion, prop.MapConversion,
prop.TargetCRS, prop.TargetCRS,
@@ -75,6 +77,7 @@ classes = (
prop.BIMMaterialProperties, prop.BIMMaterialProperties,
prop.SweptSolid, prop.SweptSolid,
prop.BIMMeshProperties, prop.BIMMeshProperties,
ui.BIM_PT_documentation,
ui.BIM_PT_bim, ui.BIM_PT_bim,
ui.BIM_PT_context, ui.BIM_PT_context,
ui.BIM_PT_qa, 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_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) 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.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary)
bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion) bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion)
bpy.types.Scene.TargetCRS = bpy.props.PointerProperty(type=prop.TargetCRS) 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_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del(bpy.types.Scene.BIMProperties) del(bpy.types.Scene.BIMProperties)
del(bpy.types.Scene.DocProperties)
del(bpy.types.Scene.MapConversion) del(bpy.types.Scene.MapConversion)
del(bpy.types.Scene.TargetCRS) del(bpy.types.Scene.TargetCRS)
del(bpy.types.Object.BIMObjectProperties) del(bpy.types.Object.BIMObjectProperties)
+76 -18
View File
@@ -1,11 +1,19 @@
import os import os
import math import math
import svgwrite
import time import time
import numpy import numpy
import pickle import pickle
import sys
from pathlib import Path 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.gp
import OCC.Geom import OCC.Geom
import OCC.Bnd import OCC.Bnd
@@ -43,11 +51,14 @@ class IfcCutter:
self.product_shapes = [] self.product_shapes = []
self.background_elements = [] self.background_elements = []
self.cut_polygons = [] self.cut_polygons = []
self.data_dir = ''
self.ifc_files = [] self.ifc_files = []
self.unit = None self.unit = None
self.resolved_pixels = set() self.resolved_pixels = set()
self.should_get_background = False self.should_get_background = False
self.pickle_file = 'shapes.pickle' self.pickle_file = 'shapes.pickle'
self.diagram_name = None
self.background_image = None
self.section_box = { self.section_box = {
'projection': (0, 1, 0), 'projection': (0, 1, 0),
'x_axis': (1, 0, 0), 'x_axis': (1, 0, 0),
@@ -103,7 +114,7 @@ class IfcCutter:
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
def load_ifc_files(self): 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)) print('Loading file {} ...'.format(filename))
self.ifc_files.append(ifcopenshell.open(filename)) self.ifc_files.append(ifcopenshell.open(filename))
@@ -558,6 +569,24 @@ class IfcCutterDebug(IfcCutter):
ifcopenshell.geom.utils.display_shape(element['geometry_face']) ifcopenshell.geom.utils.display_shape(element['geometry_face'])
input('Debug: showing background elements.') 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(): class SvgWriter():
def __init__(self, ifc_cutter): def __init__(self, ifc_cutter):
self.ifc_cutter = ifc_cutter self.ifc_cutter = ifc_cutter
@@ -565,27 +594,52 @@ class SvgWriter():
def write(self): def write(self):
self.calculate_scale() 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, debug=False,
size=('{}mm'.format(self.ifc_cutter.section_box['x'] * self.scale), size=('{}mm'.format(self.width), '{}mm'.format(self.height)),
'{}mm'.format(self.ifc_cutter.section_box['y'] * self.scale)), viewBox=('0 0 {} {}'.format(self.width, self.height)))
viewBox=('0 0 {} {}'.format(
self.ifc_cutter.section_box['x'] * self.scale,
self.ifc_cutter.section_box['y'] * self.scale)))
self.add_stylesheet() self.add_stylesheet()
self.add_defs()
self.draw_background_image()
self.draw_background_elements() self.draw_background_elements()
self.draw_cut_polygons() self.draw_cut_polygons()
self.draw_annotations()
self.svg.save(pretty=True) self.svg.save(pretty=True)
def calculate_scale(self): def calculate_scale(self):
# TODO: properly handle units # TODO: properly handle units
if self.ifc_cutter.unit.Name == 'METRE': if self.ifc_cutter.unit.Name == 'METRE':
self.scale *= 1000 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): 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())) 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): def draw_background_elements(self):
for element in self.ifc_cutter.background_elements: for element in self.ifc_cutter.background_elements:
if element['type'] == 'polygon': if element['type'] == 'polygon':
@@ -595,6 +649,19 @@ class SvgWriter():
elif element['type'] == 'line': elif element['type'] == 'line':
self.draw_line(element, 'background') 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): 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')
@@ -636,12 +703,3 @@ class SvgWriter():
classes.append('material-{}'.format(association.RelatingMaterial.Name)) classes.append('material-{}'.format(association.RelatingMaterial.Name))
classes.append('globalid-{}'.format(element.GlobalId)) classes.append('globalid-{}'.format(element.GlobalId))
return classes 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 ifcopenshell
from . import export_ifc from . import export_ifc
from . import import_ifc from . import import_ifc
from . import cut_ifc
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
from itertools import cycle from itertools import cycle
from mathutils import Vector
class ExportIFC(bpy.types.Operator): class ExportIFC(bpy.types.Operator):
bl_idname = "export.ifc" bl_idname = "export.ifc"
@@ -751,3 +753,59 @@ class RemoveSubcontext(bpy.types.Operator):
subcontext_index = int(subcontext_index) subcontext_index = int(subcontext_index)
getattr(bpy.context.scene.BIMProperties, '{}_subcontexts'.format(context)).remove(subcontext_index) getattr(bpy.context.scene.BIMProperties, '{}_subcontexts'.format(context)).remove(subcontext_index)
return {'FINISHED'} 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') target_view: StringProperty(name='Target View')
class DocProperties(PropertyGroup):
blah: StringProperty(name="Blah")
class BIMProperties(PropertyGroup): class BIMProperties(PropertyGroup):
schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory") 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") 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') 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): class BIM_PT_context(Panel):
bl_label = "Geometric Representation Contexts" bl_label = "Geometric Representation Contexts"
bl_idname = "BIM_PT_context" bl_idname = "BIM_PT_context"