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-11-16 08:54:16 +11:00
|
|
|
import blenderbim.core.drawing as core
|
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
|
2022-05-01 14:43:00 +10:00
|
|
|
from mathutils import Vector
|
2022-06-21 12:14:15 +02:00
|
|
|
from timeit import default_timer as timer
|
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
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
class profile:
|
|
|
|
|
"""
|
|
|
|
|
A python context manager timing utility
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, task):
|
|
|
|
|
self.task = task
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
self.start = timer()
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *args):
|
|
|
|
|
print(self.task, timer() - self.start)
|
|
|
|
|
|
|
|
|
|
|
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:
|
2022-04-26 09:53:32 +10:00
|
|
|
subprocess.Popen(command)
|
2021-05-16 22:15:55 +10:00
|
|
|
else:
|
|
|
|
|
webbrowser.open("file://" + path)
|
|
|
|
|
|
|
|
|
|
|
2021-11-16 08:54:16 +11:00
|
|
|
class Operator:
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
IfcStore.execute_ifc_operator(self, context)
|
|
|
|
|
blenderbim.bim.handler.refresh_ui_data()
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
2022-01-29 21:11:23 +11:00
|
|
|
class AddDrawing(bpy.types.Operator, Operator):
|
2021-03-04 22:04:40 +11:00
|
|
|
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-07-06 15:17:55 +10:00
|
|
|
def _execute(self, context):
|
2022-01-29 21:11:23 +11:00
|
|
|
self.props = context.scene.DocProperties
|
2022-02-04 08:14:36 +11:00
|
|
|
hint = self.props.location_hint
|
|
|
|
|
if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
|
|
|
|
hint = int(hint)
|
2022-02-03 17:28:09 +11:00
|
|
|
core.add_drawing(
|
|
|
|
|
tool.Ifc,
|
|
|
|
|
tool.Collector,
|
|
|
|
|
tool.Drawing,
|
|
|
|
|
target_view=self.props.target_view,
|
2022-02-04 08:14:36 +11:00
|
|
|
location_hint=hint,
|
2022-02-03 17:28:09 +11:00
|
|
|
)
|
2022-03-29 17:04:50 +11:00
|
|
|
try:
|
|
|
|
|
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
|
|
|
|
|
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2021-03-10 17:47:36 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
2022-01-29 21:11:23 +11:00
|
|
|
and camera
|
2021-09-09 21:27:23 +10:00
|
|
|
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()
|
2022-06-21 12:14:15 +02:00
|
|
|
|
|
|
|
|
with profile("Drawing generation process"):
|
|
|
|
|
|
|
|
|
|
with profile("Initialize drawing generation process"):
|
|
|
|
|
self.props = context.scene.DocProperties
|
|
|
|
|
self.cprops = self.camera.data.BIMCameraProperties
|
|
|
|
|
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
|
|
|
|
self.get_scale()
|
|
|
|
|
if self.cprops.update_representation(self.camera):
|
|
|
|
|
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
|
|
|
|
|
|
|
|
|
self.svg_writer = svgwriter.SvgWriter()
|
|
|
|
|
self.svg_writer.human_scale = self.human_scale
|
|
|
|
|
self.svg_writer.scale = self.scale
|
|
|
|
|
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
|
|
|
|
self.svg_writer.camera = self.camera
|
|
|
|
|
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
|
|
|
|
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
|
|
|
|
|
|
|
|
|
with profile("Generate underlay"):
|
|
|
|
|
underlay_svg = self.generate_underlay(context)
|
2022-07-06 18:29:22 +10:00
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("Generate linework"):
|
|
|
|
|
linework_svg = self.generate_linework(context)
|
|
|
|
|
|
|
|
|
|
with profile("Generate annotation"):
|
|
|
|
|
annotation_svg = self.generate_annotation(context)
|
2022-07-06 18:29:22 +10:00
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("Combine SVG layers"):
|
|
|
|
|
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
|
|
|
|
|
2021-08-17 14:55:19 +02:00
|
|
|
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
2022-06-21 12:14:15 +02:00
|
|
|
|
2021-05-14 17:48:53 +10:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2022-05-07 17:57:05 +10:00
|
|
|
def get_camera_dimensions(self):
|
|
|
|
|
render = bpy.context.scene.render
|
|
|
|
|
if self.is_landscape(render):
|
|
|
|
|
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
|
|
|
|
|
return width, height
|
|
|
|
|
|
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:
|
2022-05-07 20:28:24 +10:00
|
|
|
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
|
|
|
|
|
self.svg_writer.create_blank_svg(svg_path).define_boilerplate(
|
|
|
|
|
pset.get("Stylesheet"), pset.get("Markers"), pset.get("Symbols"), pset.get("Patterns")
|
|
|
|
|
)
|
|
|
|
|
boilerplate = self.svg_writer.svg.tostring()
|
2022-05-07 17:57:05 +10:00
|
|
|
outfile.write(boilerplate.replace("</svg>", ""))
|
2021-05-28 14:33:46 +10:00
|
|
|
if underlay:
|
|
|
|
|
with open(underlay) as infile:
|
2022-05-22 21:32:48 +10:00
|
|
|
for i, line in enumerate(infile):
|
2022-05-07 17:57:05 +10:00
|
|
|
if i < 2:
|
|
|
|
|
continue
|
|
|
|
|
elif "</svg>" in line:
|
2021-05-28 14:33:46 +10:00
|
|
|
continue
|
|
|
|
|
outfile.write(line)
|
|
|
|
|
shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
|
|
|
|
|
if linework:
|
|
|
|
|
with open(linework) as infile:
|
|
|
|
|
should_skip = False
|
|
|
|
|
for i, line in enumerate(infile):
|
2022-05-07 17:57:05 +10:00
|
|
|
if i == 0:
|
2021-05-28 14:33:46 +10:00
|
|
|
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)
|
|
|
|
|
if annotation:
|
|
|
|
|
with open(annotation) as infile:
|
|
|
|
|
for i, line in enumerate(infile):
|
2022-05-07 17:57:05 +10:00
|
|
|
if i < 2:
|
2021-05-28 14:33:46 +10:00
|
|
|
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):
|
2022-04-15 11:45:23 +10:00
|
|
|
if not self.cprops.has_underlay:
|
2021-05-28 14:33:46 +10:00
|
|
|
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"
|
2022-04-23 17:25:39 +10:00
|
|
|
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
|
2021-05-28 14:33:46 +10:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2022-05-07 17:57:05 +10:00
|
|
|
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
|
2021-05-28 14:33:46 +10:00
|
|
|
return svg_path
|
|
|
|
|
|
|
|
|
|
def generate_linework(self, context):
|
2022-04-15 11:45:23 +10:00
|
|
|
if not self.cprops.has_linework:
|
2021-05-28 14:33:46 +10:00
|
|
|
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
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("sync"):
|
|
|
|
|
# All very hackish whilst prototyping
|
|
|
|
|
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
|
|
|
|
exporter.file = tool.Ifc.get()
|
|
|
|
|
invalidated_guids = exporter.sync_deletions()
|
|
|
|
|
invalidated_elements = exporter.sync_all_objects()
|
|
|
|
|
invalidated_elements += exporter.sync_edited_objects()
|
|
|
|
|
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
2021-11-08 21:00:10 +11:00
|
|
|
|
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 = []
|
2022-02-04 12:04:20 +11:00
|
|
|
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
2021-11-08 08:33:19 +11:00
|
|
|
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
|
|
|
|
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
2022-02-04 12:04:20 +11:00
|
|
|
if rep_context.TargetView == target_view:
|
2021-11-08 08:33:19 +11:00
|
|
|
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
|
|
|
|
|
|
2022-04-24 22:15:14 +10:00
|
|
|
elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
2021-11-08 08:33:19 +11:00
|
|
|
|
|
|
|
|
self.setup_serialiser(ifc)
|
2021-11-10 13:52:14 +11:00
|
|
|
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
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("Processing target view context"):
|
|
|
|
|
if target_view_contexts and elements:
|
|
|
|
|
geom_settings = ifcopenshell.geom.settings(
|
|
|
|
|
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
|
|
|
|
)
|
|
|
|
|
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):
|
|
|
|
|
processed.add(ifc.by_id(elem.id))
|
|
|
|
|
self.serialiser.write(elem)
|
|
|
|
|
elements -= processed
|
|
|
|
|
|
|
|
|
|
with profile("Processing body context"):
|
|
|
|
|
if body_contexts and elements:
|
|
|
|
|
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
|
|
|
|
geom_settings.set_context_ids(body_contexts)
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
with profile("Camera element"):
|
|
|
|
|
# @todo tfk: is this needed?
|
2022-03-27 19:04:53 +11:00
|
|
|
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
2022-06-21 12:14:15 +02:00
|
|
|
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
2021-11-08 08:33:19 +11:00
|
|
|
for elem in self.yield_from_iterator(it):
|
|
|
|
|
self.serialiser.write(elem)
|
2021-10-27 22:10:52 +11:00
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("Finalizing"):
|
|
|
|
|
self.serialiser.finalize()
|
2021-11-08 08:33:19 +11:00
|
|
|
results = self.svg_buffer.get_value()
|
2021-10-30 22:13:25 +11:00
|
|
|
|
2022-06-21 12:14:15 +02:00
|
|
|
with profile("Post processing"):
|
|
|
|
|
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):
|
2022-03-27 19:04:53 +11:00
|
|
|
self.svg_settings = ifcopenshell.geom.settings(
|
|
|
|
|
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=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)
|
2022-01-14 10:54:27 +11:00
|
|
|
self.serialiser.setUseHlrPoly(True)
|
|
|
|
|
self.serialiser.setProfileThreshold(64)
|
2021-11-08 08:33:19 +11:00
|
|
|
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"))
|
2022-02-04 19:21:24 +11:00
|
|
|
if group:
|
|
|
|
|
group[:] = reversed(group)
|
2021-10-30 22:13:25 +11:00
|
|
|
|
|
|
|
|
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):
|
2022-04-15 11:45:23 +10:00
|
|
|
if not self.cprops.has_annotation:
|
2021-05-28 14:33:46 +10:00
|
|
|
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
|
|
|
|
2022-04-20 16:27:55 +10:00
|
|
|
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
|
2022-05-07 17:57:05 +10:00
|
|
|
annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
|
|
|
|
|
|
|
|
|
|
self.svg_writer.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
|
|
|
|
self.svg_writer.create_blank_svg(svg_path).draw_annotations(annotations).save()
|
2021-05-14 17:48:53 +10:00
|
|
|
|
2022-05-07 17:57:05 +10:00
|
|
|
return svg_path
|
2021-05-14 17:48:53 +10:00
|
|
|
|
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
|
|
|
|
2022-02-04 19:21:24 +11:00
|
|
|
class AddAnnotation(bpy.types.Operator, Operator):
|
2021-05-14 17:48:53 +10:00
|
|
|
bl_idname = "bim.add_annotation"
|
|
|
|
|
bl_label = "Add Annotation"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-11-16 08:35:49 +11:00
|
|
|
object_type: bpy.props.StringProperty()
|
2021-05-14 17:48:53 +10:00
|
|
|
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-07-06 15:17:55 +10:00
|
|
|
def _execute(self, context):
|
2022-02-04 19:21:24 +11:00
|
|
|
drawing = tool.Ifc.get_entity(context.scene.camera)
|
|
|
|
|
if not drawing:
|
2022-06-21 12:14:15 +02:00
|
|
|
self.report({"WARNING"}, "Not a BIM camera")
|
2022-02-04 19:21:24 +11:00
|
|
|
return
|
2022-06-21 12:14:15 +02:00
|
|
|
r = core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
|
|
|
|
if isinstance(r, str):
|
|
|
|
|
self.report({"WARNING"}, r)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-01-29 13:42:46 +11:00
|
|
|
class AddSheet(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.add_sheet"
|
|
|
|
|
bl_label = "Add Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-01-29 13:42:46 +11:00
|
|
|
def _execute(self, context):
|
|
|
|
|
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-01-29 13:42:46 +11:00
|
|
|
class OpenSheet(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.open_sheet"
|
|
|
|
|
bl_label = "Open Sheet"
|
2022-01-29 13:42:46 +11:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-01-29 13:42:46 +11:00
|
|
|
def _execute(self, context):
|
|
|
|
|
self.props = context.scene.DocProperties
|
|
|
|
|
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
|
|
|
|
|
core.open_sheet(tool.Drawing, sheet=sheet)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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"}
|
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
|
2022-03-30 18:14:51 +11:00
|
|
|
active_drawing = props.drawings[props.active_drawing_index]
|
|
|
|
|
active_sheet = props.sheets[props.active_sheet_index]
|
|
|
|
|
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
|
|
|
|
|
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
|
|
|
|
if not sheet.is_a("IfcDocumentInformation"):
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
if tool.Ifc.get_schema() == "IFC2X3":
|
|
|
|
|
references = sheet.DocumentReferences or []
|
|
|
|
|
else:
|
|
|
|
|
references = sheet.HasDocumentReferences or []
|
|
|
|
|
|
|
|
|
|
has_drawing = False
|
|
|
|
|
for reference in references:
|
|
|
|
|
if tool.Ifc.get_schema() == "IFC2X3":
|
|
|
|
|
element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
|
|
|
|
|
0
|
|
|
|
|
].RelatedObjects[0]
|
|
|
|
|
else:
|
|
|
|
|
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
|
|
|
|
|
if element == drawing:
|
|
|
|
|
has_drawing = True
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if has_drawing:
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
|
|
|
|
if tool.Ifc.get_schema() == "IFC2X3":
|
|
|
|
|
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
|
|
|
|
|
else:
|
|
|
|
|
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
|
|
|
|
|
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
|
|
|
|
tool.Ifc.run("document.assign_document", product=drawing, document=reference)
|
|
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
|
|
|
|
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
2022-04-29 13:32:34 +10:00
|
|
|
sheet_builder.add_drawing(reference, drawing, sheet)
|
2022-03-30 18:14:51 +11:00
|
|
|
|
|
|
|
|
tool.Drawing.import_sheets()
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoveDrawingFromSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.remove_drawing_from_sheet"
|
|
|
|
|
bl_label = "Remove Drawing From Sheet"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
reference: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
reference = tool.Ifc.get().by_id(self.reference)
|
2022-04-29 13:32:34 +10:00
|
|
|
sheet = tool.Drawing.get_reference_document(reference)
|
|
|
|
|
|
|
|
|
|
sheet_builder = sheeter.SheetBuilder()
|
|
|
|
|
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
|
|
|
|
sheet_builder.remove_drawing(reference, sheet)
|
|
|
|
|
|
2022-04-14 15:19:59 +10:00
|
|
|
drawing = tool.Drawing.get_reference_element(reference)
|
2022-04-29 13:32:34 +10:00
|
|
|
if drawing:
|
|
|
|
|
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
|
2022-03-30 18:14:51 +11:00
|
|
|
|
|
|
|
|
tool.Ifc.run("document.remove_reference", reference=reference)
|
|
|
|
|
|
|
|
|
|
tool.Drawing.import_sheets()
|
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
|
2022-03-30 18:14:51 +11:00
|
|
|
active_sheet = props.sheets[props.active_sheet_index]
|
2022-04-12 08:29:07 +10:00
|
|
|
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
2022-04-29 13:32:34 +10:00
|
|
|
|
|
|
|
|
if not sheet.is_a("IfcDocumentInformation"):
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2022-04-28 19:57:47 +10:00
|
|
|
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
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
|
2022-04-12 08:29:07 +10:00
|
|
|
sheet_builder.build(sheet)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
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 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"}
|
2022-01-29 17:26:09 +11:00
|
|
|
drawing: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
2022-01-29 17:26:09 +11:00
|
|
|
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(self.drawing))
|
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-11-16 08:41:21 +11:00
|
|
|
|
|
|
|
|
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
|
2021-05-17 15:15:25 +10:00
|
|
|
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
|
2022-03-29 20:30:10 +11:00
|
|
|
bpy.context.scene.DocProperties.active_drawing_id = self.drawing
|
2021-05-17 15:15:25 +10:00
|
|
|
bpy.ops.bim.activate_drawing_style()
|
2022-03-18 16:00:49 +11:00
|
|
|
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
2021-05-17 15:15:25 +10:00
|
|
|
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 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"}
|
|
|
|
|
|
|
|
|
|
|
2022-02-04 10:54:18 +11:00
|
|
|
class RemoveDrawing(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.remove_drawing"
|
|
|
|
|
bl_label = "Remove Drawing"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2022-03-29 17:04:50 +11:00
|
|
|
drawing: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-02-04 10:54:18 +11:00
|
|
|
def _execute(self, context):
|
2022-03-29 17:04:50 +11:00
|
|
|
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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"}
|
|
|
|
|
|
|
|
|
|
|
2022-01-29 15:45:45 +11:00
|
|
|
class RemoveSheet(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.remove_sheet"
|
|
|
|
|
bl_label = "Remove Sheet"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2022-03-30 18:14:51 +11:00
|
|
|
sheet: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-01-29 15:45:45 +11:00
|
|
|
def _execute(self, context):
|
2022-03-30 18:14:51 +11:00
|
|
|
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
class AddSchedule(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.add_schedule"
|
|
|
|
|
bl_label = "Add Schedule"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2022-04-28 14:16:39 +10:00
|
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
|
filter_glob: bpy.props.StringProperty(default="*.ods;*.xls;*.xlsx", options={"HIDDEN"})
|
|
|
|
|
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
def _execute(self, context):
|
|
|
|
|
filepath = self.filepath
|
|
|
|
|
if self.use_relative_path:
|
|
|
|
|
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
|
|
|
|
core.add_schedule(
|
|
|
|
|
tool.Ifc,
|
|
|
|
|
tool.Drawing,
|
|
|
|
|
uri=filepath,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
|
context.window_manager.fileselect_add(self)
|
|
|
|
|
return {"RUNNING_MODAL"}
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
class RemoveSchedule(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.remove_schedule"
|
|
|
|
|
bl_label = "Remove Schedule"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2022-04-28 14:16:39 +10:00
|
|
|
schedule: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
def _execute(self, context):
|
|
|
|
|
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
class OpenSchedule(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.open_schedule"
|
|
|
|
|
bl_label = "Open Schedule"
|
2021-07-06 15:17:55 +10:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
2022-04-28 14:16:39 +10:00
|
|
|
schedule: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
def _execute(self, context):
|
|
|
|
|
core.open_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
2022-04-28 19:57:47 +10:00
|
|
|
class BuildSchedule(bpy.types.Operator, Operator):
|
2021-05-17 15:15:25 +10:00
|
|
|
bl_idname = "bim.build_schedule"
|
|
|
|
|
bl_label = "Build Schedule"
|
2022-04-28 19:57:47 +10:00
|
|
|
schedule: bpy.props.IntProperty()
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2022-04-28 19:57:47 +10:00
|
|
|
def _execute(self, context):
|
|
|
|
|
core.build_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2022-04-29 13:32:34 +10:00
|
|
|
active_schedule = props.schedules[props.active_schedule_index]
|
|
|
|
|
active_sheet = props.sheets[props.active_sheet_index]
|
|
|
|
|
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
|
|
|
|
|
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
|
|
|
|
if not sheet.is_a("IfcDocumentInformation"):
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
if tool.Ifc.get_schema() == "IFC2X3":
|
|
|
|
|
references = sheet.DocumentReferences or []
|
|
|
|
|
else:
|
|
|
|
|
references = sheet.HasDocumentReferences or []
|
|
|
|
|
|
|
|
|
|
has_schedule = False
|
|
|
|
|
for reference in references:
|
|
|
|
|
if reference.Location == tool.Drawing.get_schedule_location(schedule):
|
|
|
|
|
has_schedule = True
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if has_schedule:
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
|
|
|
|
if tool.Ifc.get_schema() == "IFC2X3":
|
|
|
|
|
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
|
|
|
|
|
else:
|
|
|
|
|
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
|
|
|
|
|
attributes["Location"] = tool.Drawing.get_schedule_location(schedule)
|
|
|
|
|
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
|
|
|
|
|
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
|
2022-04-29 13:32:34 +10:00
|
|
|
sheet_builder.add_schedule(reference, schedule, sheet)
|
|
|
|
|
|
|
|
|
|
tool.Drawing.import_sheets()
|
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 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):
|
2022-03-18 15:49:56 +11:00
|
|
|
drawing = tool.Ifc.get_entity(context.scene.camera)
|
|
|
|
|
target_view = tool.Drawing.get_drawing_target_view(drawing)
|
2021-11-12 15:16:10 +11:00
|
|
|
subcontext = ifcopenshell.util.representation.get_context(
|
2022-03-18 15:49:56 +11:00
|
|
|
IfcStore.get_file(), "Plan", "Annotation", target_view
|
2021-11-12 15:16:10 +11:00
|
|
|
)
|
|
|
|
|
if not subcontext:
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
2021-05-17 15:15:25 +10:00
|
|
|
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
|
2022-03-18 15:49:56 +11:00
|
|
|
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
|
|
|
|
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def grep(coll):
|
2021-11-12 15:16:10 +11:00
|
|
|
results = []
|
|
|
|
|
for obj in coll.all_objects:
|
|
|
|
|
element = tool.Ifc.get_entity(obj)
|
|
|
|
|
if element and element.is_a("IfcAnnotation") and element.ObjectType == "GRID":
|
|
|
|
|
results.append(obj)
|
|
|
|
|
return results
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
def clone(src):
|
|
|
|
|
dst = src.copy()
|
|
|
|
|
dst.data = dst.data.copy()
|
2021-11-12 15:16:10 +11:00
|
|
|
dst.name = dst.name.replace("IfcGridAxis/", "")
|
|
|
|
|
dst.BIMObjectProperties.ifc_definition_id = 0
|
|
|
|
|
dst.data.BIMMeshProperties.ifc_definition_id = 0
|
2021-05-17 15:15:25 +10:00
|
|
|
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)
|
|
|
|
|
|
2021-11-12 15:16:10 +11:00
|
|
|
grids = []
|
|
|
|
|
for element in tool.Ifc.get().by_type("IfcGridAxis"):
|
|
|
|
|
obj = tool.Ifc.get_object(element)
|
|
|
|
|
if not obj:
|
|
|
|
|
continue
|
|
|
|
|
grids.append((*localize(*disassemble(clone(obj))), element))
|
2021-05-17 15:15:25 +10:00
|
|
|
|
|
|
|
|
if clipping:
|
2021-11-12 15:16:10 +11:00
|
|
|
grids = [(obj, clip(mesh), element) for obj, mesh, element in grids]
|
2021-05-17 15:15:25 +10:00
|
|
|
elif elevating:
|
2021-11-12 15:16:10 +11:00
|
|
|
grids = [(obj, elev(mesh), element) for obj, mesh, element in grids]
|
2021-05-17 15:15:25 +10:00
|
|
|
|
2021-11-12 15:16:10 +11:00
|
|
|
for obj, mesh, element in grids:
|
2021-05-17 15:15:25 +10:00
|
|
|
if mesh is not None:
|
|
|
|
|
view_coll.objects.link(assemble(obj, mesh))
|
2021-11-12 15:16:10 +11:00
|
|
|
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
|
|
|
|
|
annotation = tool.Ifc.get_entity(obj)
|
|
|
|
|
annotation.Name = element.AxisTag
|
|
|
|
|
annotation.ObjectType = "GRID"
|
2021-05-17 15:15:25 +10:00
|
|
|
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):
|
2022-08-27 10:36:54 -04:00
|
|
|
# leftmost and rightmost points of camera view area, local coords
|
2021-05-17 15:15:25 +10:00
|
|
|
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"}
|
2021-11-16 08:54:16 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class EditText(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.edit_text"
|
|
|
|
|
bl_label = "Edit Text"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.edit_text(tool.Ifc, tool.Drawing, obj=context.active_object)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EnableEditingText(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.enable_editing_text"
|
|
|
|
|
bl_label = "Enable Editing Text"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.enable_editing_text(tool.Drawing, obj=context.active_object)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DisableEditingText(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.disable_editing_text"
|
|
|
|
|
bl_label = "Disable Editing Text"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.disable_editing_text(tool.Drawing, obj=context.active_object)
|
2022-01-14 18:00:08 +11:00
|
|
|
|
|
|
|
|
|
2022-05-06 15:35:39 +10:00
|
|
|
class EditAssignedProduct(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.edit_assigned_product"
|
2022-01-14 18:00:08 +11:00
|
|
|
bl_label = "Edit Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
product = None
|
2022-05-06 15:35:39 +10:00
|
|
|
if context.active_object.BIMAssignedProductProperties.relating_product:
|
|
|
|
|
product = tool.Ifc.get_entity(context.active_object.BIMAssignedProductProperties.relating_product)
|
|
|
|
|
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
|
2022-01-14 18:00:08 +11:00
|
|
|
|
|
|
|
|
|
2022-05-06 15:35:39 +10:00
|
|
|
class EnableEditingAssignedProduct(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.enable_editing_assigned_product"
|
2022-01-14 18:00:08 +11:00
|
|
|
bl_label = "Enable Editing Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
2022-05-06 15:35:39 +10:00
|
|
|
core.enable_editing_assigned_product(tool.Drawing, obj=context.active_object)
|
2022-01-14 18:00:08 +11:00
|
|
|
|
|
|
|
|
|
2022-05-06 15:35:39 +10:00
|
|
|
class DisableEditingAssignedProduct(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.disable_editing_assigned_product"
|
2022-01-14 18:00:08 +11:00
|
|
|
bl_label = "Disable Editing Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
2022-05-06 15:35:39 +10:00
|
|
|
core.disable_editing_assigned_product(tool.Drawing, obj=context.active_object)
|
2022-01-29 13:42:46 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoadSheets(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.load_sheets"
|
|
|
|
|
bl_label = "Load Sheets"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.load_sheets(tool.Drawing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DisableEditingSheets(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.disable_editing_sheets"
|
|
|
|
|
bl_label = "Disable Editing Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.disable_editing_sheets(tool.Drawing)
|
2022-01-29 17:26:09 +11:00
|
|
|
|
|
|
|
|
|
2022-04-28 14:16:39 +10:00
|
|
|
class LoadSchedules(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.load_schedules"
|
|
|
|
|
bl_label = "Load Schedules"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.load_schedules(tool.Drawing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DisableEditingSchedules(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.disable_editing_schedules"
|
|
|
|
|
bl_label = "Disable Editing Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.disable_editing_schedules(tool.Drawing)
|
|
|
|
|
|
|
|
|
|
|
2022-01-29 17:26:09 +11:00
|
|
|
class LoadDrawings(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.load_drawings"
|
|
|
|
|
bl_label = "Load Drawings"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.load_drawings(tool.Drawing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DisableEditingDrawings(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.disable_editing_drawings"
|
|
|
|
|
bl_label = "Disable Editing Text Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.disable_editing_drawings(tool.Drawing)
|
2022-03-30 18:14:51 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExpandSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.expand_sheet"
|
|
|
|
|
bl_label = "Expand Sheet"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
sheet: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
props = context.scene.DocProperties
|
|
|
|
|
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
|
|
|
|
|
sheet.is_expanded = True
|
|
|
|
|
core.load_sheets(tool.Drawing)
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContractSheet(bpy.types.Operator):
|
|
|
|
|
bl_idname = "bim.contract_sheet"
|
|
|
|
|
bl_label = "Contract Sheet"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
sheet: bpy.props.IntProperty()
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
props = context.scene.DocProperties
|
|
|
|
|
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
|
|
|
|
|
sheet.is_expanded = False
|
|
|
|
|
core.load_sheets(tool.Drawing)
|
|
|
|
|
return {"FINISHED"}
|
2023-01-13 16:13:57 +01:00
|
|
|
|
|
|
|
|
class SelectAssignedProduct(bpy.types.Operator, Operator):
|
|
|
|
|
bl_idname = "bim.select_assigned_product"
|
|
|
|
|
bl_label = "Select Assigned Product"
|
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
|
|
def _execute(self, context):
|
|
|
|
|
core.select_assigned_product(tool.Drawing)
|