mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
WIP support for cutting a section with dimension annotation lines
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user