diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index b7abbc2ede..e289ea4aa1 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 08d48af630..da895f6fc8 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -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 "" in line: - continue - elif "" in line: - should_skip = True - continue - elif "" 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 "', '" xmlns:ifc="http://www.ifcopenshell.org/ns">') + if "" 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 "" in line: + continue + elif "" in line: + should_skip = True + continue + elif "" 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 "" in line: + continue + outfile.write(line) + outfile.write("") 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 diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 0cae4c6190..71abb3e6a0 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 752f4dd3fb..6ba48f1d8f 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -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} ) ) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 38d3c8f745..81c04ed4cc 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -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" diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index d36af57ed9..75ac4a361b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -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: