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.BIMTextProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
ui.BIM_PT_sheets,
@@ -4,6 +4,7 @@ import bpy
import json
import time
import bmesh
import shutil
import subprocess
import webbrowser
import ifcopenshell.util.selector
@@ -92,11 +93,13 @@ class CreateDrawing(bpy.types.Operator):
self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties
self.drawing_name = IfcStore.get_file().by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
base_svg = self.ifc_to_svg(context)
self.profile_code("Generate base layer")
annotation_svg = self.annotation_to_svg(context)
self.profile_code("Generate annotation layer")
svg_path = self.combine_svgs(context, base_svg, annotation_svg)
underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay")
linework_svg = self.generate_linework(context)
self.profile_code("Generate linework")
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")
open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
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))
self.time = time.time()
def combine_svgs(self, context, base, annotation):
def combine_svgs(self, context, underlay, linework, annotation):
# Hacky :)
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
with open(svg_path, "w") as outfile:
with open(base) as infile:
should_skip = False
for line in infile:
if "</svg>" in 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)
with open(annotation) as infile:
for i, line in enumerate(infile):
if i == 0 or i == 1:
continue
outfile.write(line)
has_boilerplate = False
if underlay:
with open(underlay) as infile:
for line in infile:
if "<svg " in line:
line = line.replace('">', '" xmlns:ifc="http://www.ifcopenshell.org/ns">')
if "</svg>" in line:
continue
outfile.write(line)
shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
has_boilerplate = True
if linework:
with open(linework) as infile:
should_skip = False
for i, line in enumerate(infile):
if has_boilerplate and i == 0:
continue
if "</svg>" in 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
def ifc_to_svg(self, context):
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-base.svg")
if os.path.isfile(svg_path) and not self.props.should_regenerate_base_layer:
def generate_underlay(self, context):
if not self.props.has_underlay:
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
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
subprocess.run(
@@ -161,9 +251,11 @@ class CreateDrawing(bpy.types.Operator):
)
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")
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
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["annotation_objs"] = self.get_annotation(svg_writer)
svg_writer.write()
svg_writer.write("annotation")
return svg_writer.output
def is_landscape(self):
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):
results = []
x = svg_writer.camera_width / 2
@@ -950,7 +1051,7 @@ class RefreshDrawingList(bpy.types.Operator):
for obj in bpy.context.scene.objects:
if not isinstance(obj.data, bpy.types.Camera):
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.name = obj.name.split("/")[1]
new.camera = obj
@@ -198,10 +198,12 @@ class DrawingStyle(PropertyGroup):
class DocProperties(PropertyGroup):
should_regenerate_base_layer: BoolProperty(name="Regenerate Base Layer", default=True)
should_regenerate_annotation_layer: BoolProperty(name="Regenerate Annotation Layer", default=True)
should_recut: BoolProperty(name="Should Recut", default=True)
should_recut_selected: BoolProperty(name="Should Recut Selected Only", default=False)
has_underlay: BoolProperty(name="Underlay", default=False)
has_linework: BoolProperty(name="Linework", default=True)
has_annotation: BoolProperty(name="Annotation", default=True)
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)
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
@@ -45,9 +45,10 @@ class SvgWriter:
self.vector_style = None
self.human_scale = "NTS"
self.annotations = {}
self.background_image = None
self.scale = 1 / 100 # 1:100
def write(self):
def write(self, layer):
self.calculate_scale()
self.svg = svgwrite.Drawing(
self.output,
@@ -58,14 +59,16 @@ class SvgWriter:
data_scale=self.human_scale,
)
self.add_stylesheet()
self.add_markers()
self.add_symbols()
self.add_patterns()
# self.draw_background_image()
# self.draw_background_elements()
# self.draw_cut_polygons()
self.draw_annotations()
if layer == "underlay":
self.draw_background_image()
elif layer == "annotation":
self.add_stylesheet()
self.add_markers()
self.add_symbols()
self.add_patterns()
# self.draw_background_elements()
# self.draw_cut_polygons()
self.draw_annotations()
self.svg.save(pretty=True)
def calculate_scale(self):
@@ -98,10 +101,9 @@ class SvgWriter:
self.svg.defs.add(External(child))
def draw_background_image(self):
return # TODO reimplement for artistic drawing options
self.svg.add(
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}
)
)
@@ -27,16 +27,17 @@ class BIM_PT_camera(Panel):
dprops = bpy.context.scene.DocProperties
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.prop(dprops, "should_extract")
@@ -68,7 +69,31 @@ class BIM_PT_camera(Panel):
row = layout.row()
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.operator("bim.add_drawing_style")
@@ -105,10 +130,6 @@ class BIM_PT_camera(Panel):
row.operator("bim.save_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):
@@ -238,6 +259,7 @@ class BIM_PT_text(Panel):
class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
@@ -67,7 +67,7 @@ class Usecase:
[0, 0, 0, 1]
]).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):
for modifier in self.settings["blender_object"].modifiers: