2021-08-17 08:55:29 +10:00
|
|
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
|
|
|
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
|
|
|
|
#
|
|
|
|
|
# This file is part of BlenderBIM Add-on.
|
|
|
|
|
#
|
|
|
|
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2021-03-10 17:47:36 +11:00
|
|
|
import os
|
2021-05-17 21:52:23 +10:00
|
|
|
import re
|
2021-03-04 22:04:40 +11:00
|
|
|
import bpy
|
2021-03-10 17:47:36 +11:00
|
|
|
import json
|
2021-05-17 21:52:23 +10:00
|
|
|
import time
|
2021-05-17 15:15:25 +10:00
|
|
|
import bmesh
|
2021-05-28 14:33:46 +10:00
|
|
|
import shutil
|
2021-03-10 17:47:36 +11:00
|
|
|
import subprocess
|
|
|
|
|
import webbrowser
|
2021-07-26 21:13:15 +10:00
|
|
|
import multiprocessing
|
2021-07-26 20:24:39 +10:00
|
|
|
import ifcopenshell
|
|
|
|
|
import ifcopenshell.geom
|
2021-05-17 15:15:25 +10:00
|
|
|
import ifcopenshell.util.selector
|
2021-05-16 18:26:48 +10:00
|
|
|
import ifcopenshell.util.representation
|
2021-08-20 08:01:37 +10:00
|
|
|
import blenderbim.bim.schema
|
2021-10-27 19:42:36 +11:00
|
|
|
import blenderbim.tool as tool
|
2021-05-14 17:48:53 +10:00
|
|
|
import blenderbim.bim.module.drawing.svgwriter as svgwriter
|
|
|
|
|
import blenderbim.bim.module.drawing.annotation as annotation
|
2021-05-17 15:15:25 +10:00
|
|
|
import blenderbim.bim.module.drawing.sheeter as sheeter
|
|
|
|
|
import blenderbim.bim.module.drawing.scheduler as scheduler
|
2021-05-17 21:52:23 +10:00
|
|
|
import blenderbim.bim.module.drawing.helper as helper
|
2021-11-08 21:00:10 +11:00
|
|
|
import blenderbim.bim.export_ifc
|
2021-11-08 08:33:19 +11:00
|
|
|
from lxml import etree
|
2021-05-14 17:48:53 +10:00
|
|
|
from mathutils import Vector, Matrix, Euler, geometry
|
2021-11-08 08:33:19 +11:00
|
|
|
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
2021-03-04 22:04:40 +11:00
|
|
|
from blenderbim.bim.ifc import IfcStore
|
2021-03-25 10:48:31 +11:00
|
|
|
from ifcopenshell.api.group.data import Data as GroupData
|
|
|
|
|
from ifcopenshell.api.pset.data import Data as PsetData
|
2021-03-04 22:04:40 +11:00
|
|
|
|
2021-03-10 17:47:36 +11:00
|
|
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
2021-03-04 22:04:40 +11:00
|
|
|
|
2021-05-16 18:26:48 +10:00
|
|
|
|
2021-05-16 22:15:55 +10:00
|
|
|
def open_with_user_command(user_command, path):
|
|
|
|
|
if user_command:
|
|
|
|
|
commands = eval(user_command)
|
|
|
|
|
for command in commands:
|
|
|
|
|
subprocess.run(command)
|
|
|
|
|
else:
|
|
|
|
|
webbrowser.open("file://" + path)
|
|
|
|
|
|
|
|
|
|
|
2021-03-04 22:04:40 +11:00
|
|
|
class AddDrawing(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_drawing"
|
|
|
|
|
bl_label = "Add Drawing"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-03-04 22:04:40 +11:00
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
return IfcStore.get_file()
|
|
|
|
|
|
2021-03-04 22:04:40 +11:00
|
|
|
def execute(self, context):
|
2021-07-06 15:17:55 +10:00
|
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
2021-05-17 21:52:23 +10:00
|
|
|
self.file = IfcStore.get_file()
|
2021-03-04 22:04:40 +11:00
|
|
|
new = context.scene.DocProperties.drawings.add()
|
|
|
|
|
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
|
|
|
|
|
if not bpy.data.collections.get("Views"):
|
|
|
|
|
context.scene.collection.children.link(bpy.data.collections.new("Views"))
|
|
|
|
|
views_collection = bpy.data.collections.get("Views")
|
|
|
|
|
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
|
|
|
|
|
views_collection.children.link(view_collection)
|
|
|
|
|
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
|
|
|
|
|
camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin
|
|
|
|
|
camera.data.type = "ORTHO"
|
|
|
|
|
camera.data.ortho_scale = 50 # The default of 6m is too small
|
|
|
|
|
camera.data.clip_end = 10 # A slightly more reasonable default
|
|
|
|
|
if context.scene.unit_settings.system == "IMPERIAL":
|
|
|
|
|
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
|
|
|
|
|
else:
|
|
|
|
|
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
|
|
|
|
|
context.scene.camera = camera
|
|
|
|
|
view_collection.objects.link(camera)
|
|
|
|
|
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
|
|
|
|
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
|
|
|
|
new.camera = camera
|
2021-03-10 17:47:36 +11:00
|
|
|
bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING")
|
2021-03-04 22:04:40 +11:00
|
|
|
bpy.ops.bim.activate_drawing_style()
|
2021-03-10 17:47:36 +11:00
|
|
|
|
|
|
|
|
bpy.ops.bim.add_group()
|
2021-05-17 21:52:23 +10:00
|
|
|
group = self.file.by_id(sorted(GroupData.groups.keys())[-1])
|
|
|
|
|
ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}})
|
|
|
|
|
bpy.ops.bim.assign_group(product=camera.name, group=group.id())
|
2021-08-20 08:01:37 +10:00
|
|
|
pset = ifcopenshell.api.run(
|
|
|
|
|
"pset.add_pset",
|
|
|
|
|
self.file,
|
|
|
|
|
**{
|
|
|
|
|
"product": self.file.by_id(camera.BIMObjectProperties.ifc_definition_id),
|
|
|
|
|
"name": "EPset_Drawing",
|
|
|
|
|
},
|
2021-03-10 17:47:36 +11:00
|
|
|
)
|
2021-08-20 08:01:37 +10:00
|
|
|
ifcopenshell.api.run(
|
|
|
|
|
"pset.edit_pset",
|
|
|
|
|
self.file,
|
|
|
|
|
**{
|
|
|
|
|
"pset": pset,
|
|
|
|
|
"properties": {"TargetView": "PLAN_VIEW", "Scale": "1/100"},
|
|
|
|
|
"pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name("EPset_Drawing"),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id)
|
2021-03-10 17:47:36 +11:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CreateDrawing(bpy.types.Operator):
|
2021-08-20 09:55:44 +02:00
|
|
|
"""Creates a svg drawing
|
|
|
|
|
|
|
|
|
|
Only available if :
|
|
|
|
|
- IFC file is created
|
|
|
|
|
- Camera is in Orthographic mode
|
|
|
|
|
"""
|
2021-09-09 21:27:23 +10:00
|
|
|
|
2021-03-10 17:47:36 +11:00
|
|
|
bl_idname = "bim.create_drawing"
|
|
|
|
|
bl_label = "Create Drawing"
|
|
|
|
|
|
2021-08-20 09:55:44 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
camera = context.scene.camera
|
2021-09-09 21:27:23 +10:00
|
|
|
return (
|
|
|
|
|
IfcStore.get_file()
|
|
|
|
|
and camera.type == "CAMERA"
|
|
|
|
|
and camera.data.type == "ORTHO"
|
2021-08-20 09:55:44 +02:00
|
|
|
and camera.BIMObjectProperties.ifc_definition_id
|
2021-09-09 21:27:23 +10:00
|
|
|
)
|
2021-08-20 09:55:44 +02:00
|
|
|
|
2021-03-10 17:47:36 +11:00
|
|
|
def execute(self, context):
|
2021-05-16 22:15:55 +10:00
|
|
|
self.camera = context.scene.camera
|
2021-10-27 22:10:52 +11:00
|
|
|
self.camera_element = tool.Ifc.get_entity(self.camera)
|
2021-05-17 21:52:23 +10:00
|
|
|
self.file = IfcStore.get_file()
|
|
|
|
|
self.time = None
|
|
|
|
|
start = time.time()
|
|
|
|
|
self.profile_code("Start drawing generation process")
|
2021-05-16 22:15:55 +10:00
|
|
|
self.props = context.scene.DocProperties
|
2021-08-20 09:55:44 +02:00
|
|
|
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
2021-10-27 19:42:36 +11:00
|
|
|
self.get_scale()
|
2021-10-30 22:13:25 +11:00
|
|
|
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
|
|
|
|
|
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
2021-05-28 14:33:46 +10:00
|
|
|
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)
|
2021-05-17 21:52:23 +10:00
|
|
|
self.profile_code("Combine SVG layers")
|
2021-08-17 14:55:19 +02:00
|
|
|
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
2021-05-17 21:52:23 +10:00
|
|
|
print("Total Time: {:.2f}".format(time.time() - start))
|
2021-05-14 17:48:53 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2021-05-17 21:52:23 +10:00
|
|
|
def profile_code(self, message):
|
|
|
|
|
if not self.time:
|
|
|
|
|
self.time = time.time()
|
|
|
|
|
print("{} :: {:.2f}".format(message, time.time() - self.time))
|
|
|
|
|
self.time = time.time()
|
|
|
|
|
|
2021-05-28 14:33:46 +10:00
|
|
|
def combine_svgs(self, context, underlay, linework, annotation):
|
2021-05-14 17:48:53 +10:00
|
|
|
# Hacky :)
|
2021-05-16 22:15:55 +10:00
|
|
|
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
|
2021-05-16 18:26:48 +10:00
|
|
|
with open(svg_path, "w") as outfile:
|
2021-05-28 14:33:46 +10:00
|
|
|
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
|
2021-10-27 19:42:36 +11:00
|
|
|
elif "</defs>" in line:
|
2021-05-28 14:33:46 +10:00
|
|
|
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>")
|
2021-05-14 17:48:53 +10:00
|
|
|
return svg_path
|
|
|
|
|
|
2021-05-28 14:33:46 +10:00
|
|
|
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")
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.render.filepath = svg_path[0:-4] + ".png"
|
|
|
|
|
drawing_style = context.scene.DocProperties.drawing_styles[
|
2021-05-28 14:33:46 +10:00
|
|
|
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)
|
2021-08-17 14:55:19 +02:00
|
|
|
for obj in context.visible_objects:
|
2021-05-28 14:33:46 +10:00
|
|
|
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)
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
space = self.get_view_3d(context.screen.areas)
|
2021-05-28 14:33:46 +10:00
|
|
|
previous_shading = space.shading.type
|
2021-08-17 14:55:19 +02:00
|
|
|
previous_format = context.scene.render.image_settings.file_format
|
2021-05-28 14:33:46 +10:00
|
|
|
space.shading.type = "RENDERED"
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.render.image_settings.file_format = "PNG"
|
2021-05-28 14:33:46 +10:00
|
|
|
bpy.ops.render.opengl(write_still=True)
|
|
|
|
|
space.shading.type = previous_shading
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.render.image_settings.file_format = previous_format
|
2021-05-28 14:33:46 +10:00
|
|
|
|
|
|
|
|
for name, value in previous_visibility.items():
|
|
|
|
|
bpy.data.objects[name].hide_set(value)
|
|
|
|
|
|
|
|
|
|
svg_writer = svgwriter.SvgWriter()
|
2021-08-17 14:55:19 +02:00
|
|
|
render = context.scene.render
|
2021-10-27 22:10:52 +11:00
|
|
|
if self.is_landscape(render):
|
2021-05-28 14:33:46 +10:00
|
|
|
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
|
2021-10-27 22:10:52 +11:00
|
|
|
svg_writer.human_scale = self.human_scale
|
|
|
|
|
svg_writer.scale = self.scale
|
2021-05-28 14:33:46 +10:00
|
|
|
svg_writer.output = svg_path
|
2021-08-17 14:55:19 +02:00
|
|
|
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
2021-05-28 14:33:46 +10:00
|
|
|
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)))
|
2021-08-17 14:55:19 +02:00
|
|
|
svg_writer.background_image = context.scene.render.filepath
|
2021-05-28 14:33:46 +10:00
|
|
|
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:
|
2021-05-16 22:15:55 +10:00
|
|
|
return svg_path
|
2021-10-27 19:42:36 +11:00
|
|
|
|
2021-11-08 21:00:10 +11:00
|
|
|
# All very hackish whilst prototyping
|
|
|
|
|
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
|
|
|
|
exporter.file = tool.Ifc.get()
|
|
|
|
|
invalidated_guids = exporter.sync_deletions()
|
2021-11-12 14:01:13 +11:00
|
|
|
invalidated_elements = exporter.sync_all_objects()
|
2021-11-08 21:00:10 +11:00
|
|
|
invalidated_elements += exporter.sync_edited_objects()
|
|
|
|
|
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
|
|
|
|
|
2021-11-08 08:33:19 +11:00
|
|
|
# If we have already calculated it in the SVG in the past, don't recalculate
|
|
|
|
|
edited_guids = set()
|
|
|
|
|
for obj in IfcStore.edited_objs:
|
|
|
|
|
element = tool.Ifc.get_entity(obj)
|
|
|
|
|
edited_guids.add(element.GlobalId) if hasattr(element, "GlobalId") else None
|
|
|
|
|
cached_linework = set()
|
|
|
|
|
if os.path.isfile(svg_path):
|
|
|
|
|
tree = etree.parse(svg_path)
|
|
|
|
|
root = tree.getroot()
|
|
|
|
|
cached_linework = {
|
|
|
|
|
el.get("{http://www.ifcopenshell.org/ns}guid")
|
|
|
|
|
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]")
|
|
|
|
|
}
|
|
|
|
|
cached_linework -= edited_guids
|
|
|
|
|
|
2021-07-26 20:24:39 +10:00
|
|
|
# This is a work in progress. See #1153 and #1564.
|
2021-11-08 08:33:19 +11:00
|
|
|
import hashlib
|
2021-10-27 19:42:36 +11:00
|
|
|
import ifcopenshell.draw
|
|
|
|
|
|
2021-11-08 08:33:19 +11:00
|
|
|
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
|
|
|
|
|
|
|
|
|
|
for ifc_path, ifc in files.items():
|
|
|
|
|
# Don't use draw.main() just whilst we're prototyping and experimenting
|
|
|
|
|
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
|
|
|
|
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
|
|
|
|
|
|
|
|
|
# Get all representation contexts to see what we're dealing with
|
|
|
|
|
# A drawing prioritises a target view context first, followed by a body context as a fallback
|
|
|
|
|
body_contexts = []
|
|
|
|
|
target_view_contexts = []
|
|
|
|
|
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
|
|
|
|
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
|
|
|
|
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
|
|
|
|
|
target_view_contexts.append(rep_context.id())
|
|
|
|
|
continue
|
|
|
|
|
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
|
|
|
|
body_contexts.append(rep_context.id())
|
|
|
|
|
continue
|
|
|
|
|
if rep_context.is_a() == "IfcGeometricRepresentationContext" and rep_context.ContextType == "Model":
|
|
|
|
|
body_contexts.append(rep_context.id())
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
|
|
|
|
|
|
|
|
|
|
self.setup_serialiser(ifc)
|
2021-11-10 13:52:14 +11:00
|
|
|
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
|
|
|
|
|
cache = IfcStore.get_cache()
|
2021-11-08 21:00:10 +11:00
|
|
|
[cache.remove(guid) for guid in invalidated_guids]
|
2021-11-08 08:33:19 +11:00
|
|
|
|
2021-11-08 21:00:10 +11:00
|
|
|
if target_view_contexts and elements:
|
2021-11-10 13:52:14 +11:00
|
|
|
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
|
2021-11-08 08:33:19 +11:00
|
|
|
geom_settings.set_context_ids(target_view_contexts)
|
|
|
|
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
|
|
|
|
it.set_cache(cache)
|
|
|
|
|
processed = set()
|
|
|
|
|
for elem in self.yield_from_iterator(it):
|
2021-11-08 21:00:10 +11:00
|
|
|
processed.add(ifc.by_id(elem.id))
|
2021-11-08 08:33:19 +11:00
|
|
|
self.serialiser.write(elem)
|
|
|
|
|
elements -= processed
|
|
|
|
|
|
2021-11-08 21:00:10 +11:00
|
|
|
if body_contexts and elements:
|
2021-11-08 08:33:19 +11:00
|
|
|
geom_settings = ifcopenshell.geom.settings(
|
2021-11-10 13:52:14 +11:00
|
|
|
DISABLE_TRIANGULATION=True,
|
2021-11-08 08:33:19 +11:00
|
|
|
)
|
2021-10-27 19:42:36 +11:00
|
|
|
geom_settings.set_context_ids(body_contexts)
|
2021-11-08 08:33:19 +11:00
|
|
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
|
|
|
|
it.set_cache(cache)
|
|
|
|
|
for elem in self.yield_from_iterator(it):
|
|
|
|
|
self.serialiser.write(elem)
|
2021-10-27 22:10:52 +11:00
|
|
|
|
2021-11-08 08:33:19 +11:00
|
|
|
geom_settings = ifcopenshell.geom.settings(
|
2021-11-10 13:52:14 +11:00
|
|
|
DISABLE_TRIANGULATION=True,
|
2021-11-08 08:33:19 +11:00
|
|
|
)
|
|
|
|
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
|
|
|
|
for elem in self.yield_from_iterator(it):
|
|
|
|
|
self.serialiser.write(elem)
|
2021-07-26 20:24:39 +10:00
|
|
|
|
2021-11-08 08:33:19 +11:00
|
|
|
self.serialiser.finalize()
|
|
|
|
|
results = self.svg_buffer.get_value()
|
2021-10-30 22:13:25 +11:00
|
|
|
|
|
|
|
|
root = etree.fromstring(results)
|
|
|
|
|
self.enrich_linework_with_metadata(root)
|
|
|
|
|
self.move_projection_to_bottom(root)
|
|
|
|
|
with open(svg_path, "wb") as svg:
|
|
|
|
|
svg.write(etree.tostring(root))
|
2021-07-26 20:24:39 +10:00
|
|
|
|
2021-05-14 17:48:53 +10:00
|
|
|
return svg_path
|
|
|
|
|
|
2021-11-08 08:33:19 +11:00
|
|
|
def setup_serialiser(self, ifc):
|
2021-11-10 13:52:14 +11:00
|
|
|
self.svg_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
|
2021-11-08 08:33:19 +11:00
|
|
|
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
|
|
|
|
|
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
|
|
|
|
|
self.serialiser.setFile(ifc)
|
|
|
|
|
self.serialiser.setWithoutStoreys(True)
|
|
|
|
|
self.serialiser.setPolygonal(True)
|
|
|
|
|
self.serialiser.setUseNamespace(True)
|
|
|
|
|
self.serialiser.setAlwaysProject(True)
|
|
|
|
|
self.serialiser.setAutoElevation(False)
|
|
|
|
|
self.serialiser.setAutoSection(False)
|
|
|
|
|
self.serialiser.setPrintSpaceNames(False)
|
|
|
|
|
self.serialiser.setPrintSpaceAreas(False)
|
|
|
|
|
self.serialiser.setDrawDoorArcs(False)
|
|
|
|
|
self.serialiser.setNoCSS(True)
|
|
|
|
|
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
|
|
|
|
|
self.serialiser.setScale(self.scale)
|
|
|
|
|
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
|
|
|
|
# tree = ifcopenshell.geom.tree()
|
|
|
|
|
# This instructs the tree to explode BReps into faces and return
|
|
|
|
|
# the style of the face when running tree.select_ray()
|
|
|
|
|
# tree.enable_face_styles(True)
|
|
|
|
|
|
|
|
|
|
def yield_from_iterator(self, it):
|
|
|
|
|
if it.initialize():
|
|
|
|
|
while True:
|
|
|
|
|
yield it.get()
|
|
|
|
|
if not it.next():
|
|
|
|
|
break
|
|
|
|
|
|
2021-10-30 22:13:25 +11:00
|
|
|
def enrich_linework_with_metadata(self, root):
|
|
|
|
|
ifc = tool.Ifc.get()
|
2021-11-08 08:33:19 +11:00
|
|
|
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
|
|
|
|
classes = ["cut"]
|
2021-11-08 21:00:10 +11:00
|
|
|
try:
|
|
|
|
|
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
|
|
|
|
except:
|
|
|
|
|
# See #1861 - freshly created guids cannot be extracted unfortunately
|
|
|
|
|
continue
|
2021-10-30 22:13:25 +11:00
|
|
|
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
|
|
|
|
if material:
|
|
|
|
|
if material.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
name = material.LayerSetName or "null"
|
|
|
|
|
else:
|
2021-11-08 08:33:19 +11:00
|
|
|
name = getattr(material, "Name", "null") or "null"
|
2021-10-30 22:13:25 +11:00
|
|
|
name = self.canonicalise_class_name(name)
|
|
|
|
|
classes.append(f"material-{name}")
|
2021-11-08 08:33:19 +11:00
|
|
|
el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
|
2021-10-30 22:13:25 +11:00
|
|
|
|
|
|
|
|
def move_projection_to_bottom(self, root):
|
|
|
|
|
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
|
2021-11-08 08:33:19 +11:00
|
|
|
group = root.find("{http://www.w3.org/2000/svg}g")
|
|
|
|
|
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
|
2021-10-30 22:13:25 +11:00
|
|
|
group[:] = reversed(group)
|
|
|
|
|
|
|
|
|
|
def canonicalise_class_name(self, name):
|
|
|
|
|
return re.sub("[^0-9a-zA-Z]+", "", name)
|
|
|
|
|
|
2021-05-28 14:33:46 +10:00
|
|
|
def generate_annotation(self, context):
|
|
|
|
|
if not self.props.has_annotation:
|
|
|
|
|
return
|
2021-05-16 22:15:55 +10:00
|
|
|
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
|
2021-05-28 14:33:46 +10:00
|
|
|
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
|
2021-05-16 22:15:55 +10:00
|
|
|
return svg_path
|
2021-05-14 17:48:53 +10:00
|
|
|
|
2021-05-16 22:15:55 +10:00
|
|
|
camera = self.camera
|
2021-05-14 17:48:53 +10:00
|
|
|
svg_writer = svgwriter.SvgWriter()
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
drawing_style = context.scene.DocProperties.drawing_styles[
|
2021-05-14 17:48:53 +10:00
|
|
|
camera.data.BIMCameraProperties.active_drawing_style_index
|
|
|
|
|
]
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
render = context.scene.render
|
|
|
|
|
if self.is_landscape(render):
|
2021-05-14 17:48:53 +10:00
|
|
|
width = camera.data.ortho_scale
|
|
|
|
|
height = width / render.resolution_x * render.resolution_y
|
|
|
|
|
else:
|
|
|
|
|
height = camera.data.ortho_scale
|
|
|
|
|
width = height / render.resolution_y * render.resolution_x
|
|
|
|
|
|
2021-10-27 22:10:52 +11:00
|
|
|
svg_writer.human_scale = self.human_scale
|
2021-10-27 19:42:36 +11:00
|
|
|
svg_writer.scale = self.scale
|
2021-05-16 22:15:55 +10:00
|
|
|
svg_writer.output = svg_path
|
2021-08-17 14:55:19 +02:00
|
|
|
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
2021-05-14 17:48:53 +10:00
|
|
|
svg_writer.vector_style = drawing_style.vector_style
|
|
|
|
|
svg_writer.camera = camera
|
|
|
|
|
svg_writer.camera_width = width
|
|
|
|
|
svg_writer.camera_height = height
|
|
|
|
|
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
|
|
|
|
|
|
|
|
|
for obj in camera.users_collection[0].objects:
|
|
|
|
|
if "IfcGrid" in obj.name:
|
|
|
|
|
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
|
|
|
|
|
elif obj.type == "CAMERA":
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if "IfcAnnotation/" not in obj.name:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if "Leader" in obj.name:
|
|
|
|
|
svg_writer.annotations["leader_obj"] = (obj, obj.data)
|
|
|
|
|
elif "Stair" in obj.name:
|
|
|
|
|
svg_writer.annotations["stair_obj"] = obj
|
|
|
|
|
elif "Equal" in obj.name:
|
|
|
|
|
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
|
|
|
|
|
elif "Dimension" in obj.name:
|
|
|
|
|
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
|
|
|
|
|
elif "Break" in obj.name:
|
|
|
|
|
svg_writer.annotations["break_obj"] = obj
|
|
|
|
|
elif "Hidden" in obj.name:
|
|
|
|
|
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
|
|
|
|
|
elif "Solid" in obj.name:
|
|
|
|
|
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
|
|
|
|
|
elif "Plan Level" in obj.name:
|
|
|
|
|
svg_writer.annotations["plan_level_obj"] = obj
|
|
|
|
|
elif "Section Level" in obj.name:
|
|
|
|
|
svg_writer.annotations["section_level_obj"] = obj
|
|
|
|
|
elif obj.type == "FONT":
|
|
|
|
|
svg_writer.annotations.setdefault("text_objs", []).append(obj)
|
|
|
|
|
else:
|
|
|
|
|
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
|
|
|
|
|
|
|
|
|
|
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
|
2021-11-12 14:01:13 +11:00
|
|
|
# TODO: This was the old 2D annotation box checking system, prepare to deprecate
|
|
|
|
|
svg_writer.annotations["annotation_objs"] = []
|
2021-05-14 17:48:53 +10:00
|
|
|
|
2021-05-28 14:33:46 +10:00
|
|
|
svg_writer.write("annotation")
|
2021-05-14 17:48:53 +10:00
|
|
|
return svg_writer.output
|
|
|
|
|
|
2021-10-27 19:42:36 +11:00
|
|
|
def get_scale(self):
|
|
|
|
|
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
|
|
|
|
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
|
|
|
|
else:
|
|
|
|
|
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
|
|
|
|
|
|
|
|
|
if self.camera.data.BIMCameraProperties.is_nts:
|
|
|
|
|
self.human_scale = "NTS"
|
|
|
|
|
|
|
|
|
|
numerator, denominator = fraction.split("/")
|
|
|
|
|
self.scale = float(numerator) / float(denominator)
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
def is_landscape(self, render):
|
|
|
|
|
return render.resolution_x > render.resolution_y
|
2021-05-14 17:48:53 +10:00
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
def get_view_3d(self, areas):
|
|
|
|
|
for area in areas:
|
2021-05-28 14:33:46 +10:00
|
|
|
if area.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
for space in area.spaces:
|
|
|
|
|
if space.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
return space
|
|
|
|
|
|
2021-05-17 21:52:23 +10:00
|
|
|
def get_classes(self, element, position, svg_writer):
|
|
|
|
|
classes = [position, element.is_a()]
|
2021-06-21 12:21:46 +10:00
|
|
|
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
2021-05-17 21:52:23 +10:00
|
|
|
if material:
|
|
|
|
|
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))))
|
|
|
|
|
classes.append("globalid-{}".format(element.GlobalId))
|
|
|
|
|
for attribute in svg_writer.annotations["attributes"]:
|
|
|
|
|
result = self.selector.get_element_value(element, attribute)
|
|
|
|
|
if result:
|
|
|
|
|
classes.append(
|
|
|
|
|
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
|
|
|
|
|
)
|
|
|
|
|
return classes
|
|
|
|
|
|
|
|
|
|
def get_material_name(self, element):
|
|
|
|
|
if hasattr(element, "Name") and element.Name:
|
|
|
|
|
return element.Name
|
|
|
|
|
elif hasattr(element, "LayerSetName") and element.LayerSetName:
|
|
|
|
|
return element.LayerSetName
|
|
|
|
|
return "mat-" + str(element.id())
|
|
|
|
|
|
2021-05-14 17:48:53 +10:00
|
|
|
|
|
|
|
|
class AddAnnotation(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_annotation"
|
|
|
|
|
bl_label = "Add Annotation"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-14 17:48:53 +10:00
|
|
|
obj_name: bpy.props.StringProperty()
|
|
|
|
|
data_type: bpy.props.StringProperty()
|
|
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
return IfcStore.get_file() and context.scene.camera
|
|
|
|
|
|
2021-05-14 17:48:53 +10:00
|
|
|
def execute(self, context):
|
2021-07-06 15:17:55 +10:00
|
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
2021-10-27 22:10:52 +11:00
|
|
|
context.scene.DocProperties.should_draw_decorations = True
|
2021-05-16 18:26:48 +10:00
|
|
|
subcontext = ifcopenshell.util.representation.get_context(
|
|
|
|
|
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
|
|
|
|
|
)
|
|
|
|
|
if not subcontext:
|
|
|
|
|
return {"FINISHED"}
|
2021-05-14 17:48:53 +10:00
|
|
|
if self.data_type == "text":
|
2021-07-29 23:08:17 +02:00
|
|
|
if context.selected_objects:
|
|
|
|
|
for selected_object in context.selected_objects:
|
|
|
|
|
obj = annotation.Annotator.add_text(context, related_element=selected_object)
|
2021-05-14 17:48:53 +10:00
|
|
|
else:
|
2021-07-29 23:08:17 +02:00
|
|
|
obj = annotation.Annotator.add_text(context)
|
2021-05-14 17:48:53 +10:00
|
|
|
else:
|
2021-07-29 23:08:17 +02:00
|
|
|
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context)
|
2021-05-14 17:48:53 +10:00
|
|
|
if self.obj_name == "Break":
|
2021-07-29 23:08:17 +02:00
|
|
|
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
|
2021-05-14 17:48:53 +10:00
|
|
|
else:
|
2021-07-29 23:08:17 +02:00
|
|
|
obj = annotation.Annotator.add_line_to_annotation(obj, context)
|
2021-05-16 18:26:48 +10:00
|
|
|
|
|
|
|
|
if not obj.BIMObjectProperties.ifc_definition_id:
|
|
|
|
|
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
|
|
|
|
|
else:
|
2021-05-18 17:48:44 +10:00
|
|
|
bpy.ops.bim.update_representation(obj=obj.name)
|
2021-05-16 18:26:48 +10:00
|
|
|
|
2021-05-14 17:48:53 +10:00
|
|
|
bpy.ops.object.select_all(action="DESELECT")
|
2021-08-17 14:55:19 +02:00
|
|
|
context.view_layer.objects.active = obj
|
2021-05-16 18:26:48 +10:00
|
|
|
obj.select_set(True)
|
2021-05-14 17:48:53 +10:00
|
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
2021-03-04 22:04:40 +11:00
|
|
|
return {"FINISHED"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_sheet"
|
|
|
|
|
bl_label = "Add Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
scene = context.scene
|
|
|
|
|
new = scene.DocProperties.sheets.add()
|
|
|
|
|
new.name = "{} - SHEET".format(len(scene.DocProperties.sheets))
|
2021-05-17 15:15:25 +10:00
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
2021-08-17 14:55:19 +02:00
|
|
|
sheet_builder.data_dir = scene.BIMProperties.data_dir
|
|
|
|
|
sheet_builder.create(new.name, scene.DocProperties.titleblock)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OpenSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.open_sheet"
|
|
|
|
|
bl_label = "Open Sheet"
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
open_with_user_command(
|
2021-08-17 14:55:19 +02:00
|
|
|
context.preferences.addons["blenderbim"].preferences.svg_command,
|
2021-09-09 21:27:23 +10:00
|
|
|
os.path.join(context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"),
|
2021-05-17 15:15:25 +10:00
|
|
|
)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddDrawingToSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_drawing_to_sheet"
|
|
|
|
|
bl_label = "Add Drawing To Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-09-01 12:05:20 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
props = context.scene.DocProperties
|
|
|
|
|
return props.drawings and props.sheets and context.scene.BIMProperties.data_dir
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
2021-08-17 14:55:19 +02:00
|
|
|
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
2021-09-01 12:05:20 +02:00
|
|
|
sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CreateSheets(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.create_sheets"
|
|
|
|
|
bl_label = "Create Sheets"
|
2021-07-06 15:17:55 +10:00
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-09-01 12:05:20 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
return context.scene.DocProperties.sheets and context.scene.BIMProperties.data_dir
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
scene = context.scene
|
|
|
|
|
props = scene.DocProperties
|
2021-08-23 00:07:29 +02:00
|
|
|
name = props.active_sheet.name
|
2021-05-17 15:15:25 +10:00
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
2021-08-17 14:55:19 +02:00
|
|
|
sheet_builder.data_dir = scene.BIMProperties.data_dir
|
2021-05-17 15:15:25 +10:00
|
|
|
sheet_builder.build(name)
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
|
|
|
|
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
if svg2pdf_command:
|
2021-08-17 14:55:19 +02:00
|
|
|
path = os.path.join(scene.BIMProperties.data_dir, "build", name)
|
2021-05-17 15:15:25 +10:00
|
|
|
svg = os.path.join(path, name + ".svg")
|
|
|
|
|
pdf = os.path.join(path, name + ".pdf")
|
|
|
|
|
# With great power comes great responsibility. Example:
|
|
|
|
|
# [['inkscape', svg, '-o', pdf]]
|
|
|
|
|
commands = eval(svg2pdf_command)
|
|
|
|
|
for command in commands:
|
|
|
|
|
subprocess.run(command)
|
|
|
|
|
|
|
|
|
|
if svg2dxf_command:
|
2021-08-17 14:55:19 +02:00
|
|
|
path = os.path.join(scene.BIMProperties.data_dir, "build", name)
|
2021-05-17 15:15:25 +10:00
|
|
|
svg = os.path.join(path, name + ".svg")
|
|
|
|
|
eps = os.path.join(path, name + ".eps")
|
|
|
|
|
dxf = os.path.join(path, name + ".dxf")
|
|
|
|
|
base = os.path.join(path, name)
|
|
|
|
|
# With great power comes great responsibility. Example:
|
|
|
|
|
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
|
|
|
|
|
commands = eval(svg2dxf_command)
|
|
|
|
|
for command in commands:
|
|
|
|
|
subprocess.run(command)
|
|
|
|
|
|
|
|
|
|
if svg2pdf_command:
|
2021-08-17 14:55:19 +02:00
|
|
|
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
2021-05-17 15:15:25 +10:00
|
|
|
else:
|
|
|
|
|
open_with_user_command(
|
2021-08-17 14:55:19 +02:00
|
|
|
context.preferences.addons["blenderbim"].preferences.svg_command,
|
|
|
|
|
os.path.join(scene.BIMProperties.data_dir, "build", name, name + ".svg"),
|
2021-05-17 15:15:25 +10:00
|
|
|
)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OpenView(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.open_view"
|
|
|
|
|
bl_label = "Open View"
|
|
|
|
|
view: bpy.props.StringProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
open_with_user_command(
|
2021-08-17 14:55:19 +02:00
|
|
|
context.preferences.addons["blenderbim"].preferences.svg_command,
|
|
|
|
|
os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"),
|
2021-05-17 15:15:25 +10:00
|
|
|
)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OpenViewCamera(bpy.types.Operator):
|
2021-08-23 00:07:29 +02:00
|
|
|
"""Select this drawing's camera object and expand its drawing properties"""
|
2021-09-09 21:27:23 +10:00
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.open_view_camera"
|
|
|
|
|
bl_label = "Open View Camera"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
view_name: bpy.props.StringProperty()
|
|
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
2021-09-01 12:05:20 +02:00
|
|
|
return context.mode == "OBJECT"
|
2021-08-23 00:07:29 +02:00
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
2021-08-23 00:07:29 +02:00
|
|
|
doc_props = context.scene.DocProperties
|
|
|
|
|
doc_props.active_drawing_index = doc_props.drawings.find(self.view_name)
|
|
|
|
|
drawing = doc_props.active_drawing
|
2021-05-17 15:15:25 +10:00
|
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
|
|
|
drawing.camera.select_set(True)
|
2021-08-23 00:07:29 +02:00
|
|
|
context.view_layer.objects.active = drawing.camera
|
2021-08-17 14:55:19 +02:00
|
|
|
for area in context.screen.areas:
|
2021-05-17 15:15:25 +10:00
|
|
|
if area.ui_type == "PROPERTIES":
|
|
|
|
|
for space in area.spaces:
|
|
|
|
|
space.context = "DATA"
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ActivateView(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.activate_view"
|
|
|
|
|
bl_label = "Activate View"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
drawing_index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
camera = context.scene.DocProperties.drawings[self.drawing_index].camera
|
2021-05-17 15:15:25 +10:00
|
|
|
if not camera:
|
|
|
|
|
return {"FINISHED"}
|
2021-08-17 14:55:19 +02:00
|
|
|
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
2021-05-28 16:09:42 +10:00
|
|
|
is_local_view = area.spaces[0].local_view is not None
|
|
|
|
|
if is_local_view:
|
|
|
|
|
bpy.ops.view3d.localview()
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.camera = camera
|
2021-05-28 16:09:42 +10:00
|
|
|
bpy.ops.view3d.localview()
|
|
|
|
|
else:
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.camera = camera
|
2021-05-17 15:15:25 +10:00
|
|
|
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
|
|
|
|
views_collection = bpy.data.collections.get("Views")
|
|
|
|
|
for collection in views_collection.children:
|
|
|
|
|
# We assume the project collection is at the top level
|
2021-08-17 14:55:19 +02:00
|
|
|
for project_collection in context.view_layer.layer_collection.children:
|
2021-05-17 15:15:25 +10:00
|
|
|
# We assume a convention that the 'Views' collection is directly
|
|
|
|
|
# in the project collection
|
|
|
|
|
if (
|
|
|
|
|
"Views" in project_collection.children
|
|
|
|
|
and collection.name in project_collection.children["Views"].children
|
|
|
|
|
):
|
|
|
|
|
project_collection.children["Views"].children[collection.name].hide_viewport = True
|
|
|
|
|
bpy.data.collections.get(collection.name).hide_render = True
|
2021-08-17 14:55:19 +02:00
|
|
|
context.view_layer.layer_collection.children["Views"].children[
|
2021-05-17 15:15:25 +10:00
|
|
|
camera.users_collection[0].name
|
|
|
|
|
].hide_viewport = False
|
|
|
|
|
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
|
|
|
|
|
bpy.ops.bim.activate_drawing_style()
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SelectDocIfcFile(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.select_doc_ifc_file"
|
|
|
|
|
bl_label = "Select Documentation IFC File"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-08-23 00:07:29 +02:00
|
|
|
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
2021-05-17 15:15:25 +10:00
|
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.DocProperties.ifc_files[self.index].name = self.filepath
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
|
context.window_manager.fileselect_add(self)
|
|
|
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateReferences(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.generate_references"
|
|
|
|
|
bl_label = "Generate References"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
self.camera = context.scene.camera
|
2021-05-17 15:15:25 +10:00
|
|
|
self.filter_potential_references()
|
|
|
|
|
if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW":
|
|
|
|
|
self.generate_grids()
|
|
|
|
|
if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW":
|
|
|
|
|
self.generate_grids()
|
2021-07-29 23:08:17 +02:00
|
|
|
self.generate_levels(context)
|
2021-05-17 15:15:25 +10:00
|
|
|
if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW":
|
|
|
|
|
self.generate_grids()
|
2021-07-29 23:08:17 +02:00
|
|
|
self.generate_levels(context)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
def filter_potential_references(self):
|
|
|
|
|
for name in ["grids", "levels"]:
|
|
|
|
|
setattr(self, name, [])
|
|
|
|
|
for obj in bpy.data.objects:
|
|
|
|
|
if "IfcGridAxis/" in obj.name:
|
|
|
|
|
self.grids.append(obj)
|
|
|
|
|
if "IfcBuildingStorey/" in obj.name:
|
|
|
|
|
self.levels.append(obj)
|
|
|
|
|
|
|
|
|
|
def generate_grids(self):
|
|
|
|
|
# TODO
|
|
|
|
|
pass
|
|
|
|
|
|
2021-07-29 23:08:17 +02:00
|
|
|
def generate_levels(self, context):
|
2021-05-17 15:15:25 +10:00
|
|
|
if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y:
|
|
|
|
|
width = self.camera.data.ortho_scale
|
|
|
|
|
height = (
|
|
|
|
|
width / self.camera.data.BIMCameraProperties.raster_x * self.camera.data.BIMCameraProperties.raster_y
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
height = self.camera.data.ortho_scale
|
|
|
|
|
width = (
|
|
|
|
|
height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x
|
|
|
|
|
)
|
2021-07-29 23:08:17 +02:00
|
|
|
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
width_in_mm = width * 1000
|
|
|
|
|
real_world_width_in_mm = width_in_mm * scale
|
|
|
|
|
offset_in_mm = 20
|
|
|
|
|
offset_percentage = offset_in_mm / real_world_width_in_mm
|
|
|
|
|
|
|
|
|
|
for obj in self.levels:
|
|
|
|
|
projection = self.project_point_onto_camera(obj.location)
|
|
|
|
|
co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1))
|
|
|
|
|
co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1))
|
2021-07-29 23:08:17 +02:00
|
|
|
annotation.Annotator.add_line_to_annotation(level_obj, context, co1, co2)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def project_point_onto_camera(self, point):
|
|
|
|
|
projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
|
|
|
|
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
|
|
|
|
point.xyz, point.xyz - projection, self.camera.location, projection
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResizeText(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.resize_text"
|
|
|
|
|
bl_label = "Resize Text"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
for obj in context.scene.camera.users_collection[0].objects:
|
2021-05-17 15:15:25 +10:00
|
|
|
if isinstance(obj.data, bpy.types.TextCurve):
|
|
|
|
|
annotation.Annotator.resize_text(obj)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddVariable(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_variable"
|
|
|
|
|
bl_label = "Add Variable"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
context.active_object.data.BIMTextProperties.variables.add()
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveVariable(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_variable"
|
|
|
|
|
bl_label = "Remove Variable"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
context.active_object.data.BIMTextProperties.variables.remove(self.index)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PropagateTextData(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.propagate_text_data"
|
|
|
|
|
bl_label = "Propagate Text Data"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
source = context.active_object
|
|
|
|
|
for obj in context.selected_objects:
|
2021-05-17 15:15:25 +10:00
|
|
|
if obj == source:
|
|
|
|
|
continue
|
|
|
|
|
obj.data.body = source.data.body
|
|
|
|
|
obj.data.align_x = source.data.align_x
|
|
|
|
|
obj.data.align_y = source.data.align_y
|
|
|
|
|
obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size
|
|
|
|
|
obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol
|
2021-08-17 14:41:15 +02:00
|
|
|
obj.data.BIMTextProperties.variables.clear()
|
2021-05-17 15:15:25 +10:00
|
|
|
for variable in source.data.BIMTextProperties.variables:
|
|
|
|
|
new_variable = obj.data.BIMTextProperties.variables.add()
|
|
|
|
|
new_variable.name = variable.name
|
|
|
|
|
new_variable.prop_key = variable.prop_key
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveDrawing(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_drawing"
|
|
|
|
|
bl_label = "Remove Drawing"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
camera = props.drawings[self.index].camera
|
|
|
|
|
collection = camera.users_collection[0]
|
|
|
|
|
for obj in collection.objects:
|
|
|
|
|
bpy.data.objects.remove(obj)
|
|
|
|
|
bpy.data.collections.remove(collection, do_unlink=True)
|
|
|
|
|
props.drawings.remove(self.index)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddDrawingStyle(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_drawing_style"
|
|
|
|
|
bl_label = "Add Drawing Style"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
new = context.scene.DocProperties.drawing_styles.add()
|
2021-05-17 15:15:25 +10:00
|
|
|
new.name = "New Drawing Style"
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveDrawingStyle(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_drawing_style"
|
|
|
|
|
bl_label = "Remove Drawing Style"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
context.scene.DocProperties.drawing_styles.remove(self.index)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SaveDrawingStyle(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.save_drawing_style"
|
|
|
|
|
bl_label = "Save Drawing Style"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.StringProperty()
|
2021-07-06 15:17:55 +10:00
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-18 00:50:24 +02:00
|
|
|
space = self.get_view_3d(context) # Do not remove. It is used later in eval
|
|
|
|
|
scene = context.scene
|
|
|
|
|
style = {}
|
|
|
|
|
for prop in RasterStyleProperty:
|
|
|
|
|
value = eval(prop.value)
|
|
|
|
|
if not isinstance(value, str):
|
|
|
|
|
try:
|
|
|
|
|
value = tuple(value)
|
|
|
|
|
except TypeError:
|
|
|
|
|
pass
|
|
|
|
|
style[prop.value] = value
|
2021-05-17 15:15:25 +10:00
|
|
|
if self.index:
|
|
|
|
|
index = int(self.index)
|
|
|
|
|
else:
|
2021-08-18 00:50:24 +02:00
|
|
|
index = context.active_object.data.BIMCameraProperties.active_drawing_style_index
|
|
|
|
|
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2021-08-18 00:50:24 +02:00
|
|
|
def get_view_3d(self, context):
|
|
|
|
|
for area in context.screen.areas:
|
2021-05-17 15:15:25 +10:00
|
|
|
if area.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
for space in area.spaces:
|
|
|
|
|
if space.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
return space
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ActivateDrawingStyle(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.activate_drawing_style"
|
|
|
|
|
bl_label = "Activate Drawing Style"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
scene = context.scene
|
2021-09-09 21:27:23 +10:00
|
|
|
if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles):
|
2021-08-17 14:55:19 +02:00
|
|
|
self.drawing_style = scene.DocProperties.drawing_styles[
|
|
|
|
|
scene.camera.data.BIMCameraProperties.active_drawing_style_index
|
2021-05-17 15:15:25 +10:00
|
|
|
]
|
2021-08-18 00:50:24 +02:00
|
|
|
self.set_raster_style(context)
|
|
|
|
|
self.set_query(context)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2021-08-18 00:50:24 +02:00
|
|
|
def set_raster_style(self, context):
|
|
|
|
|
scene = context.scene # Do not remove. It is used in exec later
|
|
|
|
|
space = self.get_view_3d(context) # Do not remove. It is used in exec later
|
2021-05-17 15:15:25 +10:00
|
|
|
style = json.loads(self.drawing_style.raster_style)
|
2021-08-18 00:50:24 +02:00
|
|
|
for path, value in style.items():
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
exec(f"{path} = '{value}'")
|
|
|
|
|
else:
|
|
|
|
|
exec(f"{path} = {value}")
|
|
|
|
|
|
|
|
|
|
def set_query(self, context):
|
2021-05-17 15:15:25 +10:00
|
|
|
self.selector = ifcopenshell.util.selector.Selector()
|
|
|
|
|
self.include_global_ids = []
|
|
|
|
|
self.exclude_global_ids = []
|
2021-08-18 00:50:24 +02:00
|
|
|
for ifc_file in context.scene.DocProperties.ifc_files:
|
2021-05-17 15:15:25 +10:00
|
|
|
try:
|
|
|
|
|
ifc = ifcopenshell.open(ifc_file.name)
|
|
|
|
|
except:
|
|
|
|
|
continue
|
|
|
|
|
if self.drawing_style.include_query:
|
|
|
|
|
results = self.selector.parse(ifc, self.drawing_style.include_query)
|
|
|
|
|
self.include_global_ids.extend([e.GlobalId for e in results])
|
|
|
|
|
if self.drawing_style.exclude_query:
|
|
|
|
|
results = self.selector.parse(ifc, self.drawing_style.exclude_query)
|
|
|
|
|
self.exclude_global_ids.extend([e.GlobalId for e in results])
|
|
|
|
|
if self.drawing_style.include_query:
|
2021-08-18 00:50:24 +02:00
|
|
|
self.parse_filter_query("INCLUDE", context)
|
2021-05-17 15:15:25 +10:00
|
|
|
if self.drawing_style.exclude_query:
|
2021-08-18 00:50:24 +02:00
|
|
|
self.parse_filter_query("EXCLUDE", context)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-08-18 00:50:24 +02:00
|
|
|
def parse_filter_query(self, mode, context):
|
2021-05-17 15:15:25 +10:00
|
|
|
if mode == "INCLUDE":
|
2021-08-18 00:50:24 +02:00
|
|
|
objects = context.scene.objects
|
2021-05-17 15:15:25 +10:00
|
|
|
elif mode == "EXCLUDE":
|
2021-08-18 00:50:24 +02:00
|
|
|
objects = context.visible_objects
|
2021-05-17 15:15:25 +10:00
|
|
|
for obj in objects:
|
|
|
|
|
if mode == "INCLUDE":
|
|
|
|
|
obj.hide_viewport = False # Note: this breaks alt-H
|
|
|
|
|
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
|
|
|
|
|
if not global_id:
|
|
|
|
|
continue
|
|
|
|
|
global_id = global_id.string_value
|
|
|
|
|
if mode == "INCLUDE":
|
|
|
|
|
if global_id not in self.include_global_ids:
|
|
|
|
|
obj.hide_viewport = True # Note: this breaks alt-H
|
|
|
|
|
elif mode == "EXCLUDE":
|
|
|
|
|
if global_id in self.exclude_global_ids:
|
|
|
|
|
obj.hide_viewport = True # Note: this breaks alt-H
|
|
|
|
|
|
2021-08-18 00:50:24 +02:00
|
|
|
def get_view_3d(self, context):
|
|
|
|
|
for area in context.screen.areas:
|
2021-05-17 15:15:25 +10:00
|
|
|
if area.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
for space in area.spaces:
|
|
|
|
|
if space.type != "VIEW_3D":
|
|
|
|
|
continue
|
|
|
|
|
return space
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EditVectorStyle(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.edit_vector_style"
|
|
|
|
|
bl_label = "Edit Vector Style"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
camera = context.scene.camera
|
|
|
|
|
vector_style = context.scene.DocProperties.drawing_styles[
|
|
|
|
|
camera.data.BIMCameraProperties.active_drawing_style_index
|
|
|
|
|
].vector_style
|
|
|
|
|
bpy.data.texts.load(os.path.join(context.scene.BIMProperties.data_dir, "styles", vector_style + ".css"))
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_sheet"
|
|
|
|
|
bl_label = "Remove Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
props.sheets.remove(self.index)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddSchedule(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_schedule"
|
|
|
|
|
bl_label = "Add Schedule"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
new = context.scene.DocProperties.schedules.add()
|
|
|
|
|
new.name = "SCHEDULE {}".format(len(context.scene.DocProperties.schedules))
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveSchedule(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_schedule"
|
|
|
|
|
bl_label = "Remove Schedule"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
props.schedules.remove(self.index)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SelectScheduleFile(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.select_schedule_file"
|
|
|
|
|
bl_label = "Select Documentation IFC File"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
|
filter_glob: bpy.props.StringProperty(default="*.ods", options={"HIDDEN"})
|
|
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-08-23 00:07:29 +02:00
|
|
|
props.active_schedule.file = self.filepath
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
|
context.window_manager.fileselect_add(self)
|
|
|
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BuildSchedule(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.build_schedule"
|
|
|
|
|
bl_label = "Build Schedule"
|
|
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
return context.scene.DocProperties.active_schedule.file
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-08-23 00:07:29 +02:00
|
|
|
schedule = props.active_schedule
|
2021-05-17 15:15:25 +10:00
|
|
|
schedule_creator = scheduler.Scheduler()
|
2021-08-17 14:55:19 +02:00
|
|
|
outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
|
2021-05-17 15:15:25 +10:00
|
|
|
schedule_creator.schedule(schedule.file, outfile)
|
2021-08-17 14:55:19 +02:00
|
|
|
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, outfile)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddScheduleToSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_schedule_to_sheet"
|
|
|
|
|
bl_label = "Add Schedule To Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
# TODO: check undo redo
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-09-01 12:05:20 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
props = context.scene.DocProperties
|
|
|
|
|
return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.DocProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
2021-08-17 14:55:19 +02:00
|
|
|
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
2021-09-01 12:05:20 +02:00
|
|
|
sheet_builder.add_schedule(props.active_schedule.name, props.active_sheet.name)
|
2021-05-17 15:15:25 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddDrawingStyleAttribute(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_drawing_style_attribute"
|
|
|
|
|
bl_label = "Add Drawing Style Attribute"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.camera.data.BIMCameraProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add()
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveDrawingStyleAttribute(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_drawing_style_attribute"
|
|
|
|
|
bl_label = "Remove Drawing Style Attribute"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
props = context.scene.camera.data.BIMCameraProperties
|
2021-05-17 15:15:25 +10:00
|
|
|
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RefreshDrawingList(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.refresh_drawing_list"
|
|
|
|
|
bl_label = "Refresh Drawing List"
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-20 08:01:37 +10:00
|
|
|
doc_props = context.scene.DocProperties
|
2021-08-17 14:41:15 +02:00
|
|
|
doc_props.drawings.clear()
|
|
|
|
|
for obj in context.scene.objects:
|
2021-05-17 15:15:25 +10:00
|
|
|
if not isinstance(obj.data, bpy.types.Camera):
|
|
|
|
|
continue
|
2021-05-28 14:33:46 +10:00
|
|
|
if "IfcAnnotation/" in obj.name:
|
2021-08-17 14:41:15 +02:00
|
|
|
new = doc_props.drawings.add()
|
2021-05-28 17:26:00 +10:00
|
|
|
new.name = "/".join(obj.name.split("/")[1:])
|
2021-05-17 15:15:25 +10:00
|
|
|
new.camera = obj
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CleanWireframes(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.clean_wireframes"
|
|
|
|
|
bl_label = "Clean Wireframes"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2021-08-17 14:55:19 +02:00
|
|
|
if context.selected_objects:
|
|
|
|
|
objects = context.selected_objects
|
2021-08-23 00:07:29 +02:00
|
|
|
else:
|
|
|
|
|
objects = context.scene.objects
|
|
|
|
|
for obj in (o for o in objects if o.type == "MESH"):
|
|
|
|
|
if "EDGE_SPLIT" not in (m.type for m in obj.modifiers):
|
2021-05-17 15:15:25 +10:00
|
|
|
obj.modifiers.new("EdgeSplit", "EDGE_SPLIT")
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CopyGrid(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_grid"
|
|
|
|
|
bl_label = "Add Grid"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
return helper.get_active_drawing(context.scene)[0] is not None
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
|
|
|
|
proj_coll = helper.get_project_collection(context.scene)
|
|
|
|
|
view_coll, camera = helper.get_active_drawing(context.scene)
|
|
|
|
|
is_ortho = camera.data.type == "ORTHO"
|
|
|
|
|
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
|
|
|
|
|
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
|
|
|
|
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
|
|
|
|
|
|
|
|
|
def grep(coll):
|
|
|
|
|
return [obj for obj in coll.all_objects if obj.name.startswith("IfcGridAxis")]
|
|
|
|
|
|
|
|
|
|
def clone(src):
|
|
|
|
|
dst = src.copy()
|
|
|
|
|
dst.data = dst.data.copy()
|
|
|
|
|
return dst
|
|
|
|
|
|
|
|
|
|
def disassemble(obj):
|
|
|
|
|
mesh = bmesh.new()
|
|
|
|
|
mesh.verts.ensure_lookup_table()
|
|
|
|
|
mesh.from_mesh(obj.data)
|
|
|
|
|
return obj, mesh
|
|
|
|
|
|
|
|
|
|
def assemble(obj, mesh):
|
|
|
|
|
mesh.to_mesh(obj.data)
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
def localize(obj, mesh):
|
|
|
|
|
# convert to camera coords
|
|
|
|
|
mesh.transform(camera.matrix_world.inverted() @ obj.matrix_world)
|
|
|
|
|
obj.matrix_world = camera.matrix_world
|
|
|
|
|
return obj, mesh
|
|
|
|
|
|
|
|
|
|
def clip(mesh):
|
|
|
|
|
# clip segment against camera view bounds
|
|
|
|
|
mesh.verts.ensure_lookup_table()
|
|
|
|
|
points = [v.co for v in mesh.verts[0:2]]
|
|
|
|
|
points = helper.clip_segment(bounds, points)
|
|
|
|
|
if points is None:
|
|
|
|
|
return None
|
|
|
|
|
mesh.verts[0].co = points[0]
|
|
|
|
|
mesh.verts[1].co = points[1]
|
|
|
|
|
return mesh
|
|
|
|
|
|
|
|
|
|
def elev(mesh):
|
|
|
|
|
# put camera-perpendicular segments vertically
|
|
|
|
|
mesh.verts.ensure_lookup_table()
|
|
|
|
|
points = [v.co for v in mesh.verts[0:2]]
|
|
|
|
|
points = helper.elevate_segment(bounds, points)
|
|
|
|
|
if points is None:
|
|
|
|
|
return None
|
|
|
|
|
points = helper.clip_segment(bounds, points)
|
|
|
|
|
if points is None:
|
|
|
|
|
return None
|
|
|
|
|
mesh.verts[0].co = points[0]
|
|
|
|
|
mesh.verts[1].co = points[1]
|
|
|
|
|
return mesh
|
|
|
|
|
|
|
|
|
|
for obj in grep(view_coll):
|
|
|
|
|
view_coll.objects.unlink(obj)
|
|
|
|
|
|
|
|
|
|
grid = [localize(*disassemble(clone(obj))) for obj in grep(proj_coll)]
|
|
|
|
|
|
|
|
|
|
if clipping:
|
|
|
|
|
grid = [(obj, clip(mesh)) for obj, mesh in grid]
|
|
|
|
|
elif elevating:
|
|
|
|
|
grid = [(obj, elev(mesh)) for obj, mesh in grid]
|
|
|
|
|
|
|
|
|
|
for obj, mesh in grid:
|
|
|
|
|
if mesh is not None:
|
|
|
|
|
view_coll.objects.link(assemble(obj, mesh))
|
|
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddSectionsAnnotations(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.add_sections_annotations"
|
|
|
|
|
bl_label = "Add Sections"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-08-23 00:07:29 +02:00
|
|
|
@classmethod
|
2021-09-09 21:27:23 +10:00
|
|
|
def poll(cls, context):
|
2021-08-23 00:07:29 +02:00
|
|
|
camera = helper.get_active_drawing(context.scene)[1]
|
|
|
|
|
return camera and camera.data.type == "ORTHO"
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
def execute(self, context):
|
|
|
|
|
scene = context.scene
|
|
|
|
|
view_coll, camera = helper.get_active_drawing(scene)
|
2021-08-23 00:07:29 +02:00
|
|
|
bounds = helper.ortho_view_frame(camera.data)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
drawings = [
|
|
|
|
|
d
|
|
|
|
|
for d in scene.DocProperties.drawings
|
|
|
|
|
if (
|
|
|
|
|
d.camera is not camera
|
|
|
|
|
and d.camera.data.type == "ORTHO"
|
|
|
|
|
and d.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW"
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def sideview(cam):
|
|
|
|
|
# leftmost and righmost points of camera view area, local coords
|
|
|
|
|
xmin, xmax, _, _, _, _ = helper.ortho_view_frame(cam.data, margin=0)
|
|
|
|
|
proj = camera.matrix_world.inverted() @ cam.matrix_world
|
|
|
|
|
p_l = proj @ Vector((xmin, 0, 0))
|
|
|
|
|
p_r = proj @ Vector((xmax, 0, 0))
|
|
|
|
|
return helper.clip_segment(bounds, (p_l, p_r))
|
|
|
|
|
|
|
|
|
|
def annotation(drawing, points):
|
|
|
|
|
# object with path geometry
|
|
|
|
|
name = drawing.name
|
|
|
|
|
curve = bpy.data.curves.new(f"Section/{name}", "CURVE")
|
|
|
|
|
spline = curve.splines.new("POLY") # has 1 initial point
|
|
|
|
|
spline.points.add(1)
|
|
|
|
|
p1, p2 = points
|
|
|
|
|
z = bounds[4] - 1e-5 # zmin
|
|
|
|
|
spline.points[0].co = (p1.x, p1.y, z, 1)
|
|
|
|
|
spline.points[1].co = (p2.x, p2.y, z, 1)
|
|
|
|
|
obj = bpy.data.objects.new(f"IfcAnnotation/Section/{name}", curve)
|
|
|
|
|
obj.matrix_world = camera.matrix_world
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
for d in drawings:
|
|
|
|
|
points = sideview(d.camera)
|
|
|
|
|
if points is None:
|
|
|
|
|
continue
|
|
|
|
|
obj = annotation(d, points)
|
|
|
|
|
view_coll.objects.link(obj)
|
|
|
|
|
|
|
|
|
|
return {"FINISHED"}
|