Reimplement raster underlays when generating drawings. See #1153.

This commit is contained in:
Dion Moult
2021-05-28 14:33:46 +10:00
parent 1cd243f13c
commit b8e20ec1a5
6 changed files with 190 additions and 62 deletions
@@ -45,6 +45,7 @@ classes = (
prop.BIMCameraProperties, prop.BIMCameraProperties,
prop.BIMTextProperties, prop.BIMTextProperties,
ui.BIM_PT_camera, ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_drawings, ui.BIM_PT_drawings,
ui.BIM_PT_schedules, ui.BIM_PT_schedules,
ui.BIM_PT_sheets, ui.BIM_PT_sheets,
@@ -4,6 +4,7 @@ import bpy
import json import json
import time import time
import bmesh import bmesh
import shutil
import subprocess import subprocess
import webbrowser import webbrowser
import ifcopenshell.util.selector import ifcopenshell.util.selector
@@ -92,11 +93,13 @@ class CreateDrawing(bpy.types.Operator):
self.profile_code("Start drawing generation process") self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties self.props = context.scene.DocProperties
self.drawing_name = IfcStore.get_file().by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name self.drawing_name = IfcStore.get_file().by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
base_svg = self.ifc_to_svg(context) underlay_svg = self.generate_underlay(context)
self.profile_code("Generate base layer") self.profile_code("Generate underlay")
annotation_svg = self.annotation_to_svg(context) linework_svg = self.generate_linework(context)
self.profile_code("Generate annotation layer") self.profile_code("Generate linework")
svg_path = self.combine_svgs(context, base_svg, annotation_svg) annotation_svg = self.generate_annotation(context)
self.profile_code("Generate annotation")
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
self.profile_code("Combine SVG layers") self.profile_code("Combine SVG layers")
open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path) open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
print("Total Time: {:.2f}".format(time.time() - start)) print("Total Time: {:.2f}".format(time.time() - start))
@@ -108,34 +111,121 @@ class CreateDrawing(bpy.types.Operator):
print("{} :: {:.2f}".format(message, time.time() - self.time)) print("{} :: {:.2f}".format(message, time.time() - self.time))
self.time = time.time() self.time = time.time()
def combine_svgs(self, context, base, annotation): def combine_svgs(self, context, underlay, linework, annotation):
# Hacky :) # Hacky :)
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg") svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
with open(svg_path, "w") as outfile: with open(svg_path, "w") as outfile:
with open(base) as infile: has_boilerplate = False
should_skip = False if underlay:
for line in infile: with open(underlay) as infile:
if "</svg>" in line: for line in infile:
continue if "<svg " in line:
elif "<defs>" in line: line = line.replace('">', '" xmlns:ifc="http://www.ifcopenshell.org/ns">')
should_skip = True if "</svg>" in line:
continue continue
elif "</style>" in line: outfile.write(line)
should_skip = False shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
continue has_boilerplate = True
elif should_skip: if linework:
continue with open(linework) as infile:
outfile.write(line) should_skip = False
with open(annotation) as infile: for i, line in enumerate(infile):
for i, line in enumerate(infile): if has_boilerplate and i == 0:
if i == 0 or i == 1: continue
continue if "</svg>" in line:
outfile.write(line) continue
elif "<defs>" in line:
should_skip = True
continue
elif "</style>" in line:
should_skip = False
continue
elif should_skip:
continue
outfile.write(line)
has_boilerplate = True
if annotation:
with open(annotation) as infile:
for i, line in enumerate(infile):
if has_boilerplate and i in [0, 1]:
continue
if "</svg>" in line:
continue
outfile.write(line)
outfile.write("</svg>")
return svg_path return svg_path
def ifc_to_svg(self, context): def generate_underlay(self, context):
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-base.svg") if not self.props.has_underlay:
if os.path.isfile(svg_path) and not self.props.should_regenerate_base_layer: return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
bpy.context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = bpy.context.scene.DocProperties.drawing_styles[
self.camera.data.BIMCameraProperties.active_drawing_style_index
]
if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True)
else:
previous_visibility = {}
for obj in self.camera.users_collection[0].objects:
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
for obj in bpy.context.visible_objects:
if (
(not obj.data and not obj.instance_collection)
or isinstance(obj.data, bpy.types.Camera)
or "IfcGrid/" in obj.name
or "IfcGridAxis/" in obj.name
or "IfcOpeningElement/" in obj.name
):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
space = self.get_view_3d()
previous_shading = space.shading.type
previous_format = bpy.context.scene.render.image_settings.file_format
space.shading.type = "RENDERED"
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.ops.render.opengl(write_still=True)
space.shading.type = previous_shading
bpy.context.scene.render.image_settings.file_format = previous_format
for name, value in previous_visibility.items():
bpy.data.objects[name].hide_set(value)
svg_writer = svgwriter.SvgWriter()
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
if self.camera.data.BIMCameraProperties.is_nts:
svg_writer.human_scale = "NTS"
else:
svg_writer.human_scale = human_scale
render = bpy.context.scene.render
if self.is_landscape():
width = self.camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = self.camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.output = svg_path
svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
svg_writer.camera = self.camera
svg_writer.camera_width = width
svg_writer.camera_height = height
svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
svg_writer.background_image = bpy.context.scene.render.filepath
svg_writer.write("underlay")
return svg_path
def generate_linework(self, context):
if not self.props.has_linework:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path return svg_path
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert") ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
subprocess.run( subprocess.run(
@@ -161,9 +251,11 @@ class CreateDrawing(bpy.types.Operator):
) )
return svg_path return svg_path
def annotation_to_svg(self, context): def generate_annotation(self, context):
if not self.props.has_annotation:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg") svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
if os.path.isfile(svg_path) and not self.props.should_regenerate_annotation_layer: if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
return svg_path return svg_path
camera = self.camera camera = self.camera
@@ -237,12 +329,21 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes] svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
svg_writer.annotations["annotation_objs"] = self.get_annotation(svg_writer) svg_writer.annotations["annotation_objs"] = self.get_annotation(svg_writer)
svg_writer.write() svg_writer.write("annotation")
return svg_writer.output return svg_writer.output
def is_landscape(self): def is_landscape(self):
return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y
def get_view_3d(self):
for area in bpy.context.screen.areas:
if area.type != "VIEW_3D":
continue
for space in area.spaces:
if space.type != "VIEW_3D":
continue
return space
def get_annotation(self, svg_writer): def get_annotation(self, svg_writer):
results = [] results = []
x = svg_writer.camera_width / 2 x = svg_writer.camera_width / 2
@@ -950,7 +1051,7 @@ class RefreshDrawingList(bpy.types.Operator):
for obj in bpy.context.scene.objects: for obj in bpy.context.scene.objects:
if not isinstance(obj.data, bpy.types.Camera): if not isinstance(obj.data, bpy.types.Camera):
continue continue
if "IfcAnnotation/" in obj.name and obj.users_collection[0].name == obj.name: if "IfcAnnotation/" in obj.name:
new = bpy.context.scene.DocProperties.drawings.add() new = bpy.context.scene.DocProperties.drawings.add()
new.name = obj.name.split("/")[1] new.name = obj.name.split("/")[1]
new.camera = obj new.camera = obj
@@ -198,10 +198,12 @@ class DrawingStyle(PropertyGroup):
class DocProperties(PropertyGroup): class DocProperties(PropertyGroup):
should_regenerate_base_layer: BoolProperty(name="Regenerate Base Layer", default=True) has_underlay: BoolProperty(name="Underlay", default=False)
should_regenerate_annotation_layer: BoolProperty(name="Regenerate Annotation Layer", default=True) has_linework: BoolProperty(name="Linework", default=True)
should_recut: BoolProperty(name="Should Recut", default=True) has_annotation: BoolProperty(name="Annotation", default=True)
should_recut_selected: BoolProperty(name="Should Recut Selected Only", default=False) should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
should_extract: BoolProperty(name="Should Extract", default=True) should_extract: BoolProperty(name="Should Extract", default=True)
drawings: CollectionProperty(name="Drawings", type=Drawing) drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex) active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
@@ -45,9 +45,10 @@ class SvgWriter:
self.vector_style = None self.vector_style = None
self.human_scale = "NTS" self.human_scale = "NTS"
self.annotations = {} self.annotations = {}
self.background_image = None
self.scale = 1 / 100 # 1:100 self.scale = 1 / 100 # 1:100
def write(self): def write(self, layer):
self.calculate_scale() self.calculate_scale()
self.svg = svgwrite.Drawing( self.svg = svgwrite.Drawing(
self.output, self.output,
@@ -58,14 +59,16 @@ class SvgWriter:
data_scale=self.human_scale, data_scale=self.human_scale,
) )
self.add_stylesheet() if layer == "underlay":
self.add_markers() self.draw_background_image()
self.add_symbols() elif layer == "annotation":
self.add_patterns() self.add_stylesheet()
# self.draw_background_image() self.add_markers()
# self.draw_background_elements() self.add_symbols()
# self.draw_cut_polygons() self.add_patterns()
self.draw_annotations() # self.draw_background_elements()
# 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):
@@ -98,10 +101,9 @@ class SvgWriter:
self.svg.defs.add(External(child)) self.svg.defs.add(External(child))
def draw_background_image(self): def draw_background_image(self):
return # TODO reimplement for artistic drawing options
self.svg.add( self.svg.add(
self.svg.image( self.svg.image(
os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)), os.path.join("..", "diagrams", os.path.basename(self.background_image)),
**{"width": self.width, "height": self.height} **{"width": self.width, "height": self.height}
) )
) )
@@ -27,16 +27,17 @@ class BIM_PT_camera(Panel):
dprops = bpy.context.scene.DocProperties dprops = bpy.context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties props = context.active_object.data.BIMCameraProperties
layout.label(text="Generation Options:") col = layout.column(align=True)
row = col.row(align=True)
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
row = layout.row()
row.prop(dprops, "should_regenerate_base_layer")
row = layout.row()
row.prop(dprops, "should_regenerate_annotation_layer")
row = layout.row()
row.prop(dprops, "should_recut")
row = layout.row()
row.prop(dprops, "should_recut_selected")
row = layout.row() row = layout.row()
row.prop(dprops, "should_extract") row.prop(dprops, "should_extract")
@@ -68,7 +69,31 @@ class BIM_PT_camera(Panel):
row = layout.row() row = layout.row()
row.prop(props, "custom_diagram_scale") row.prop(props, "custom_diagram_scale")
layout.label(text="Drawing Styles:") row = layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
op = row.operator("bim.open_view", icon="URL", text="")
op.view = context.active_object.name.split("/")[1]
class BIM_PT_drawing_underlay(Panel):
bl_label = "Drawing Underlay"
bl_idname = "BIM_PT_drawing_underlay"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_parent_id = "BIM_PT_camera"
@classmethod
def poll(cls, context):
engine = context.engine
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
dprops = bpy.context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
row = layout.row(align=True) row = layout.row(align=True)
row.operator("bim.add_drawing_style") row.operator("bim.add_drawing_style")
@@ -105,10 +130,6 @@ class BIM_PT_camera(Panel):
row.operator("bim.save_drawing_style") row.operator("bim.save_drawing_style")
row.operator("bim.activate_drawing_style") row.operator("bim.activate_drawing_style")
row = layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing")
op = row.operator("bim.open_view", icon="URL", text="")
op.view = context.active_object.name.split("/")[1]
class BIM_PT_drawings(Panel): class BIM_PT_drawings(Panel):
@@ -238,6 +259,7 @@ class BIM_PT_text(Panel):
class BIM_PT_annotation_utilities(Panel): class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities" bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation" bl_label = "Annotation"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
bl_category = "BlenderBIM" bl_category = "BlenderBIM"
@@ -67,7 +67,7 @@ class Usecase:
[0, 0, 0, 1] [0, 0, 0, 1]
]).inverted() ]).inverted()
return any([abs(tM @ v.co).z) > threshold for v in face.verts]) return any([abs((tM @ v.co).z) > threshold for v in face.verts])
def evaluate_geometry(self): def evaluate_geometry(self):
for modifier in self.settings["blender_object"].modifiers: for modifier in self.settings["blender_object"].modifiers: