mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Add section cut multiprocessing, grids, hidden lines, stair arrows, text annotations, and leaders
This commit is contained in:
@@ -11,124 +11,128 @@ bl_info = {
|
||||
"category": "Import-Export"
|
||||
}
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
# are we running inside Blender?
|
||||
import sys
|
||||
bpy = sys.modules.get('bpy')
|
||||
if bpy is not None:
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AssignClass,
|
||||
operator.SelectClass,
|
||||
operator.SelectType,
|
||||
operator.SelectFeaturesDir,
|
||||
operator.SelectDiffJsonFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.ColourByClass,
|
||||
operator.ResetObjectColours,
|
||||
operator.ApproveClass,
|
||||
operator.RejectClass,
|
||||
operator.SelectAudited,
|
||||
operator.RejectElement,
|
||||
operator.SelectExternalMaterialDir,
|
||||
operator.AddSweptSolid,
|
||||
operator.RemoveSweptSolid,
|
||||
operator.AssignSweptSolidOuterCurve,
|
||||
operator.SelectSweptSolidOuterCurve,
|
||||
operator.AddSweptSolidInnerCurve,
|
||||
operator.SelectSweptSolidInnerCurves,
|
||||
operator.AssignSweptSolidExtrusion,
|
||||
operator.SelectSweptSolidExtrusion,
|
||||
operator.AssignPset,
|
||||
operator.UnassignPset,
|
||||
operator.AddPset,
|
||||
operator.RemovePset,
|
||||
operator.AddDocument,
|
||||
operator.RemoveDocument,
|
||||
operator.GenerateGlobalId,
|
||||
operator.AddAttribute,
|
||||
operator.RemoveAttribute,
|
||||
operator.QuickProjectSetup,
|
||||
operator.SelectGlobalId,
|
||||
operator.CreateAggregate,
|
||||
operator.EditAggregate,
|
||||
operator.SaveAggregate,
|
||||
operator.AssignClassification,
|
||||
operator.UnassignClassification,
|
||||
operator.RemoveClassification,
|
||||
operator.FetchLibraryInformation,
|
||||
operator.FetchExternalMaterial,
|
||||
operator.FetchObjectPassport,
|
||||
operator.AddSubcontext,
|
||||
operator.RemoveSubcontext,
|
||||
operator.CutSection,
|
||||
operator.CreateSheets,
|
||||
prop.Subcontext,
|
||||
prop.BIMProperties,
|
||||
prop.DocProperties,
|
||||
prop.BIMLibrary,
|
||||
prop.MapConversion,
|
||||
prop.TargetCRS,
|
||||
prop.Attribute,
|
||||
prop.Pset,
|
||||
prop.Document,
|
||||
prop.Classification,
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.SweptSolid,
|
||||
prop.BIMMeshProperties,
|
||||
ui.BIM_PT_documentation,
|
||||
ui.BIM_PT_bim,
|
||||
ui.BIM_PT_context,
|
||||
ui.BIM_PT_qa,
|
||||
ui.BIM_PT_library,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_diff,
|
||||
ui.BIM_PT_mvd,
|
||||
ui.BIM_PT_material,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_object,
|
||||
)
|
||||
classes = (
|
||||
operator.AssignClass,
|
||||
operator.SelectClass,
|
||||
operator.SelectType,
|
||||
operator.SelectFeaturesDir,
|
||||
operator.SelectDiffJsonFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.ColourByClass,
|
||||
operator.ResetObjectColours,
|
||||
operator.ApproveClass,
|
||||
operator.RejectClass,
|
||||
operator.SelectAudited,
|
||||
operator.RejectElement,
|
||||
operator.SelectExternalMaterialDir,
|
||||
operator.AddSweptSolid,
|
||||
operator.RemoveSweptSolid,
|
||||
operator.AssignSweptSolidOuterCurve,
|
||||
operator.SelectSweptSolidOuterCurve,
|
||||
operator.AddSweptSolidInnerCurve,
|
||||
operator.SelectSweptSolidInnerCurves,
|
||||
operator.AssignSweptSolidExtrusion,
|
||||
operator.SelectSweptSolidExtrusion,
|
||||
operator.AssignPset,
|
||||
operator.UnassignPset,
|
||||
operator.AddPset,
|
||||
operator.RemovePset,
|
||||
operator.AddDocument,
|
||||
operator.RemoveDocument,
|
||||
operator.GenerateGlobalId,
|
||||
operator.AddAttribute,
|
||||
operator.RemoveAttribute,
|
||||
operator.QuickProjectSetup,
|
||||
operator.SelectGlobalId,
|
||||
operator.CreateAggregate,
|
||||
operator.EditAggregate,
|
||||
operator.SaveAggregate,
|
||||
operator.AssignClassification,
|
||||
operator.UnassignClassification,
|
||||
operator.RemoveClassification,
|
||||
operator.FetchLibraryInformation,
|
||||
operator.FetchExternalMaterial,
|
||||
operator.FetchObjectPassport,
|
||||
operator.AddSubcontext,
|
||||
operator.RemoveSubcontext,
|
||||
operator.CutSection,
|
||||
operator.CreateSheets,
|
||||
prop.Subcontext,
|
||||
prop.BIMProperties,
|
||||
prop.DocProperties,
|
||||
prop.BIMLibrary,
|
||||
prop.MapConversion,
|
||||
prop.TargetCRS,
|
||||
prop.Attribute,
|
||||
prop.Pset,
|
||||
prop.Document,
|
||||
prop.Classification,
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.SweptSolid,
|
||||
prop.BIMMeshProperties,
|
||||
ui.BIM_PT_documentation,
|
||||
ui.BIM_PT_bim,
|
||||
ui.BIM_PT_context,
|
||||
ui.BIM_PT_qa,
|
||||
ui.BIM_PT_library,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_diff,
|
||||
ui.BIM_PT_mvd,
|
||||
ui.BIM_PT_material,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_object,
|
||||
)
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportIFC.bl_idname,
|
||||
text="Industry Foundation Classes (.ifc)")
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportIFC.bl_idname,
|
||||
text="Industry Foundation Classes (.ifc)")
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportIFC.bl_idname,
|
||||
text="Industry Foundation Classes (.ifc)")
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportIFC.bl_idname,
|
||||
text="Industry Foundation Classes (.ifc)")
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.app.handlers.load_post.append(prop.setDefaultProperties)
|
||||
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)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.app.handlers.load_post.append(prop.setDefaultProperties)
|
||||
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)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.app.handlers.load_post.remove(prop.setDefaultProperties)
|
||||
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)
|
||||
del(bpy.types.Collection.BIMObjectProperties)
|
||||
del(bpy.types.Material.BIMMaterialProperties)
|
||||
del(bpy.types.Mesh.BIMMeshProperties)
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.app.handlers.load_post.remove(prop.setDefaultProperties)
|
||||
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)
|
||||
del(bpy.types.Collection.BIMObjectProperties)
|
||||
del(bpy.types.Material.BIMMaterialProperties)
|
||||
del(bpy.types.Mesh.BIMMeshProperties)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
@@ -5,7 +5,9 @@ import numpy
|
||||
import pickle
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
mathutils = sys.modules.get('mathutils')
|
||||
if mathutils is not None:
|
||||
from mathutils import Vector
|
||||
from math import degrees
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
@@ -42,6 +44,80 @@ from OCC.TopoDS import topods
|
||||
import ifcopenshell
|
||||
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:
|
||||
def __init__(self):
|
||||
self.product_shapes = []
|
||||
@@ -253,10 +329,16 @@ class IfcCutter:
|
||||
self.section_box['face'] = section_box.BottomFace()
|
||||
|
||||
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(
|
||||
OCC.gp.gp_Pnt(0, 0, 0),
|
||||
OCC.gp.gp_Dir(0, 0, -1),
|
||||
OCC.gp.gp_Dir(1, 0, 0))
|
||||
self.transformation_dest = destination
|
||||
self.transformation = OCC.gp.gp_Trsf()
|
||||
self.transformation.SetDisplacement(source, destination)
|
||||
|
||||
@@ -489,51 +571,22 @@ class IfcCutter:
|
||||
return
|
||||
|
||||
total_product_shapes = len(self.product_shapes)
|
||||
n = 0
|
||||
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)
|
||||
process_data = [(p.GlobalId, s, self.section_box['face'], self.transformation_data) for p, s in self.product_shapes]
|
||||
|
||||
transformed_wire = OCC.BRepBuilderAPI.BRepBuilderAPI_Transform(
|
||||
wire_shape, self.transformation)
|
||||
wire_shape = transformed_wire.Shape()
|
||||
import multiprocessing
|
||||
import bpy
|
||||
multiprocessing.set_executable(bpy.app.binary_path_python)
|
||||
|
||||
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()
|
||||
self.cut_polygons.append({
|
||||
'global_id': product.GlobalId,
|
||||
'points': points
|
||||
})
|
||||
with multiprocessing.Pool(9) as p:
|
||||
results = p.map(do_cut, process_data)
|
||||
for result in results:
|
||||
polygons = [p for p in result if p['points']]
|
||||
self.cut_polygons.extend(polygons)
|
||||
|
||||
if not os.path.isfile(self.cut_pickle_file):
|
||||
with open(self.cut_pickle_file, 'wb') as pickle_file:
|
||||
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):
|
||||
def cut(self):
|
||||
@@ -672,37 +725,132 @@ class SvgWriter():
|
||||
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 = Vector(((x_offset + v0.x), (y_offset - v0.y)))
|
||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||
mid = ((end - start) / 2) + start
|
||||
# TODO: hardcoded meters to mm conversion, until I properly do units
|
||||
vector = end - start
|
||||
if (end.y - start.y) * self.scale > 1:
|
||||
perpendicular = Vector((-vector.y, vector.x)).normalized()
|
||||
if self.ifc_cutter.dimension_obj:
|
||||
for edge in self.ifc_cutter.dimension_obj.data.edges:
|
||||
classes = ['annotation', 'dimension']
|
||||
v0 = self.ifc_cutter.dimension_obj.data.vertices[edge.vertices[0]].co
|
||||
v1 = self.ifc_cutter.dimension_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)))
|
||||
mid = ((end - start) / 2) + start
|
||||
# TODO: hardcoded meters to mm conversion, until I properly do units
|
||||
vector = end - start
|
||||
if (end.y - start.y) * self.scale > 1:
|
||||
perpendicular = Vector((-vector.y, vector.x)).normalized()
|
||||
else:
|
||||
perpendicular = Vector((vector.y, -vector.x)).normalized()
|
||||
text_position = (mid * self.scale) + perpendicular
|
||||
dimension = vector.length * 1000
|
||||
rotation = -degrees(vector.angle(Vector((1, 0))))
|
||||
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
|
||||
end=tuple(end * self.scale), class_=' '.join(classes)))
|
||||
line['marker-start'] = 'url(#dimension-marker)'
|
||||
line['marker-end'] = 'url(#dimension-marker)'
|
||||
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
|
||||
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
|
||||
self.svg.add(self.svg.text(str(round(dimension)), insert=tuple(text_position), **{
|
||||
'transform': 'rotate({} {} {})'.format(
|
||||
rotation,
|
||||
text_position.x,
|
||||
text_position.y
|
||||
),
|
||||
'font-size': '4.13', # 2.5
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'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:
|
||||
perpendicular = Vector((vector.y, -vector.x)).normalized()
|
||||
text_position = (mid * self.scale) + perpendicular
|
||||
dimension = vector.length * 1000
|
||||
rotation = -degrees(vector.angle(Vector((1, 0))))
|
||||
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
|
||||
end=tuple(end * self.scale), class_=' '.join(classes)))
|
||||
line['marker-start'] = 'url(#dimension-marker)'
|
||||
line['marker-end'] = 'url(#dimension-marker)'
|
||||
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
|
||||
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
|
||||
self.svg.add(self.svg.text(str(round(dimension)), insert=tuple(text_position), **{
|
||||
'transform': 'rotate({} {} {})'.format(
|
||||
rotation,
|
||||
text_position.x,
|
||||
text_position.y
|
||||
),
|
||||
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': 'middle'
|
||||
'text-anchor': text_anchor,
|
||||
'alignment-baseline': alignment_baseline,
|
||||
'dominant-baseline': alignment_baseline
|
||||
}))
|
||||
|
||||
def draw_cut_polygons(self):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
.background { fill: white; 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" ?>
|
||||
<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 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;" />
|
||||
</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>
|
||||
|
||||
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 1.4 KiB |
@@ -788,9 +788,27 @@ class CutSection(bpy.types.Operator):
|
||||
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.leader_obj = None
|
||||
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 = {
|
||||
'projection': tuple(projection),
|
||||
'x_axis': tuple(x_axis),
|
||||
|
||||
Reference in New Issue
Block a user