mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
Reimplement raster underlays when generating drawings. See #1153.
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user