Drawings now load on-demand instead of at project load. See #2520.

This commit is contained in:
Dion Moult
2023-02-08 16:53:19 +11:00
parent 88bddb9531
commit ea91f4302c
7 changed files with 173 additions and 66 deletions
+13 -48
View File
@@ -236,8 +236,6 @@ class IfcImporter:
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
self.create_annotations()
self.profile_code("Create annotation")
self.parse_native_elements()
self.profile_code("Parsing native elements")
self.create_native_elements()
@@ -586,7 +584,7 @@ class IfcImporter:
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True)
grid_obj.lock_rotation = (True, True, True)
collection = bpy.data.collections.new(self.get_name(grid))
collection = bpy.data.collections.new(tool.Loader.get_name(grid))
u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes)
v_axes = bpy.data.collections.new("VAxes")
@@ -628,11 +626,11 @@ class IfcImporter:
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
self.material_creator.create(element, obj, mesh)
self.type_products[element.GlobalId] = obj
@@ -687,9 +685,6 @@ class IfcImporter:
def create_spatial_elements(self):
self.create_generic_elements(self.spatial_elements)
def create_annotations(self):
self.create_generic_elements(self.annotations)
def create_elements(self):
self.create_generic_elements(self.elements)
@@ -923,21 +918,21 @@ class IfcImporter:
pass
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
elif shape:
mesh_name = self.get_mesh_name(shape.geometry)
mesh_name = tool.Loader.get_mesh_name(shape.geometry)
mesh = self.meshes.get(mesh_name)
if mesh is None:
mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
else:
mesh = None
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
if shape:
@@ -1385,12 +1380,6 @@ class IfcImporter:
if not view_collection:
view_collection = bpy.data.collections.new("Views")
self.project["blender"].children.link(view_collection)
for element in self.file.by_type("IfcAnnotation"):
if element.ObjectType == "DRAWING":
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
collection = bpy.data.collections.new("IfcGroup/" + group.Name)
self.collections[group.GlobalId] = collection
view_collection.children.link(collection)
def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
@@ -1404,7 +1393,7 @@ class IfcImporter:
collection = obj.users_collection[0]
self.collections[element.GlobalId] = collection
if not is_existing:
collection = bpy.data.collections.new(self.get_name(element))
collection = bpy.data.collections.new(tool.Loader.get_name(element))
self.collections[element.GlobalId] = collection
parent.children.link(collection)
if element.IsDecomposedBy:
@@ -1432,7 +1421,7 @@ class IfcImporter:
aggregates = {}
for rel_aggregate in rel_aggregates:
element = rel_aggregate.RelatingObject
collection = bpy.data.collections.new(self.get_name(element))
collection = bpy.data.collections.new(tool.Loader.get_name(element))
aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection
@@ -1663,9 +1652,6 @@ class IfcImporter:
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
@@ -1813,16 +1799,6 @@ class IfcImporter:
):
return representation.Items[0].MappingTarget
def get_mesh_name(self, geometry):
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
representation = self.file.by_id(representation_id)
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
return "{}/{}".format(context_id, representation_id)
def create_camera(self, element, shape):
if hasattr(shape, "geometry"):
geometry = shape.geometry
@@ -1837,7 +1813,7 @@ class IfcImporter:
height = max(y) - min(y)
depth = max(z) - min(z)
camera = bpy.data.cameras.new(self.get_mesh_name(geometry))
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
camera.type = "ORTHO"
camera.ortho_scale = width if width > height else height
camera.clip_end = depth
@@ -1877,7 +1853,7 @@ class IfcImporter:
else:
geometry = shape
curve = bpy.data.curves.new(self.get_mesh_name(geometry), type="CURVE")
curve = bpy.data.curves.new(tool.Loader.get_mesh_name(geometry), type="CURVE")
curve.dimensions = "3D"
curve.resolution_u = 2
@@ -1903,7 +1879,7 @@ class IfcImporter:
else:
geometry = shape
mesh = bpy.data.meshes.new(self.get_mesh_name(geometry))
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
props = bpy.context.scene.BIMGeoreferenceProperties
if (
@@ -2018,17 +1994,6 @@ class IfcImporter:
self.added_data[element.id()] = obj
IfcStore.link_element(element, obj)
def link_mesh(self, shape, mesh):
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
geometry = shape
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
else:
# TODO: See #2002
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj)
@@ -154,16 +154,13 @@ class RemoveRepresentation(bpy.types.Operator, Operator):
)
class UpdateRepresentation(bpy.types.Operator):
class UpdateRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.update_representation"
bl_label = "Update Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
if context.active_object and context.active_object.mode != "OBJECT":
# Ensure mode is object to prevent invalid mesh data causing CTD
+12 -10
View File
@@ -197,7 +197,9 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
group = drawing_tool.get_drawing_group(drawing)
if drawing_tool.get_name(group) != name:
ifc.run("attribute.edit_attributes", product=group, attributes={"Name": name})
drawing_tool.set_drawing_collection_name(group, drawing_tool.get_drawing_collection(drawing))
collection = drawing_tool.get_drawing_collection(drawing)
if collection:
drawing_tool.set_drawing_collection_name(group, collection)
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
@@ -242,14 +244,13 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
should_delete_existing_annotation = False
should_create_annotation = False
if annotation and (
not reference_obj
or ifc.is_moved(reference_obj)
or ifc.is_edited(reference_obj)
or ifc.is_deleted(reference_element)
or ifc.is_deleted(annotation)
):
should_delete_existing_annotation = True
if annotation:
if ifc.is_deleted(annotation):
should_delete_existing_annotation = True
elif reference_obj and (ifc.is_moved(reference_obj) or ifc.is_edited(reference_obj)):
should_delete_existing_annotation = True
elif not reference_obj and ifc.is_deleted(reference_element):
should_delete_existing_annotation = True
if reference_obj and (should_delete_existing_annotation or not annotation):
should_create_annotation = True
@@ -279,5 +280,6 @@ def select_assigned_product(drawing):
def activate_drawing_view(ifc, drawing_tool, drawing):
camera = ifc.get_object(drawing)
if not camera:
return
camera = drawing_tool.import_drawing(drawing)
drawing_tool.import_annotations_in_group(drawing_tool.get_drawing_group(drawing))
drawing_tool.activate_view(camera)
+5
View File
@@ -308,6 +308,11 @@ class Library:
def set_editing_mode(cls, mode): pass
@interface
class Loader:
pass
@interface
class Material:
def add_default_material_object(cls): pass
@@ -31,6 +31,7 @@ from blenderbim.tool.geometry import Geometry
from blenderbim.tool.georeference import Georeference
from blenderbim.tool.ifc import Ifc
from blenderbim.tool.library import Library
from blenderbim.tool.loader import Loader
from blenderbim.tool.material import Material
from blenderbim.tool.misc import Misc
from blenderbim.tool.model import Model
+87 -4
View File
@@ -21,6 +21,7 @@ import re
import bpy
import math
import bmesh
import logging
import mathutils
import webbrowser
import subprocess
@@ -33,6 +34,7 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.prop import get_diagram_scales
class Drawing(blenderbim.core.tool.Drawing):
@@ -204,7 +206,8 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def get_drawing_collection(cls, drawing):
obj = tool.Ifc.get_object(drawing)
return obj.users_collection[0]
if obj:
return obj.users_collection[0]
@classmethod
def get_drawing_group(cls, drawing):
@@ -296,6 +299,83 @@ class Drawing(blenderbim.core.tool.Drawing):
if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct
@classmethod
def import_annotations_in_group(cls, group):
elements = set(
[e for e in cls.get_group_elements(group) if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING"]
)
logger = logging.getLogger("ImportIFC")
ifc_import_settings = blenderbim.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger)
ifc_importer = blenderbim.bim.import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = tool.Ifc.get()
ifc_importer.calculate_unit_scale()
ifc_importer.create_generic_elements(elements)
for obj in ifc_importer.added_data.values():
tool.Collector.assign(obj)
@classmethod
def import_drawing(cls, drawing):
settings = ifcopenshell.geom.settings()
settings.set(settings.STRICT_TOLERANCE, True)
shape = ifcopenshell.geom.create_shape(settings, drawing)
geometry = shape.geometry
v = geometry.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)]
z = [v[i + 2] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
depth = max(z) - min(z)
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
camera.type = "ORTHO"
camera.ortho_scale = width if width > height else height
camera.clip_end = depth
if width > height:
camera.BIMCameraProperties.raster_x = 1000
camera.BIMCameraProperties.raster_y = round(1000 * (height / width))
else:
camera.BIMCameraProperties.raster_x = round(1000 * (width / height))
camera.BIMCameraProperties.raster_y = 1000
psets = ifcopenshell.util.element.get_psets(drawing)
pset = psets.get("EPset_Drawing")
if pset:
if "TargetView" in pset:
camera.BIMCameraProperties.target_view = pset["TargetView"]
if "Scale" in pset:
valid_scales = [
i[0] for i in get_diagram_scales(None, bpy.context) if pset["Scale"] == i[0].split("|")[-1]
]
if valid_scales:
camera.BIMCameraProperties.diagram_scale = valid_scales[0]
else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
tool.Loader.link_mesh(shape, camera)
obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
tool.Ifc.link(drawing, obj)
m = shape.transformation.matrix.data
mat = mathutils.Matrix(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
obj.matrix_world = mat
tool.Geometry.record_object_position(obj)
tool.Collector.assign(obj)
return obj
@classmethod
def import_drawings(cls):
bpy.context.scene.DocProperties.drawings.clear()
@@ -923,9 +1003,12 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def is_drawing_active(cls):
camera = bpy.context.scene.camera
return (camera and camera.type == "CAMERA" and
camera.BIMObjectProperties.ifc_definition_id and
any(a.type == 'VIEW_3D' for a in bpy.context.screen.areas))
return (
camera
and camera.type == "CAMERA"
and camera.BIMObjectProperties.ifc_definition_id
and any(a.type == "VIEW_3D" for a in bpy.context.screen.areas)
)
@classmethod
def is_camera_orthographic(cls):
+54
View File
@@ -0,0 +1,54 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 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/>.
import re
import bpy
import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.tool as tool
# Progressively we'll refactor loading elements into Blender objects into this
# class. This will break down the monolithic import_ifc module and allow us to
# partially load and unload objects for huge models, partial model editing, and
# supplementary objects (e.g. drawings, structural analysis models, etc).
class Loader(blenderbim.core.tool.Loader):
@classmethod
def get_mesh_name(cls, geometry):
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
representation = tool.Ifc.get().by_id(representation_id)
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
return "{}/{}".format(context_id, representation_id)
@classmethod
def get_name(cls, element):
return "{}/{}".format(element.is_a(), element.Name)
@classmethod
def link_mesh(cls, shape, mesh):
geometry = shape.geometry if hasattr(shape, "geometry") else shape
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
else:
# TODO: See #2002
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))