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.profile_code("Create materials")
self.create_styles() self.create_styles()
self.profile_code("Create styles") self.profile_code("Create styles")
self.create_annotations()
self.profile_code("Create annotation")
self.parse_native_elements() self.parse_native_elements()
self.profile_code("Parsing native elements") self.profile_code("Parsing native elements")
self.create_native_elements() self.create_native_elements()
@@ -586,7 +584,7 @@ class IfcImporter:
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import: if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True) grid_obj.lock_location = (True, True, True)
grid_obj.lock_rotation = (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") u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes) collection.children.link(u_axes)
v_axes = bpy.data.collections.new("VAxes") v_axes = bpy.data.collections.new("VAxes")
@@ -628,11 +626,11 @@ class IfcImporter:
try: try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation) shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh = self.create_mesh(element, shape) mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh) tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh self.meshes[mesh_name] = mesh
except: except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element) 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.link_element(element, obj)
self.material_creator.create(element, obj, mesh) self.material_creator.create(element, obj, mesh)
self.type_products[element.GlobalId] = obj self.type_products[element.GlobalId] = obj
@@ -687,9 +685,6 @@ class IfcImporter:
def create_spatial_elements(self): def create_spatial_elements(self):
self.create_generic_elements(self.spatial_elements) self.create_generic_elements(self.spatial_elements)
def create_annotations(self):
self.create_generic_elements(self.annotations)
def create_elements(self): def create_elements(self):
self.create_generic_elements(self.elements) self.create_generic_elements(self.elements)
@@ -923,21 +918,21 @@ class IfcImporter:
pass pass
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape) 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: elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
mesh = self.create_curve(element, shape) mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh) tool.Loader.link_mesh(shape, mesh)
elif shape: 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) mesh = self.meshes.get(mesh_name)
if mesh is None: if mesh is None:
mesh = self.create_mesh(element, shape) mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh) tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh self.meshes[mesh_name] = mesh
else: else:
mesh = None 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) self.link_element(element, obj)
if shape: if shape:
@@ -1385,12 +1380,6 @@ class IfcImporter:
if not view_collection: if not view_collection:
view_collection = bpy.data.collections.new("Views") view_collection = bpy.data.collections.new("Views")
self.project["blender"].children.link(view_collection) 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): def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects: for element in related_objects:
@@ -1404,7 +1393,7 @@ class IfcImporter:
collection = obj.users_collection[0] collection = obj.users_collection[0]
self.collections[element.GlobalId] = collection self.collections[element.GlobalId] = collection
if not is_existing: 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 self.collections[element.GlobalId] = collection
parent.children.link(collection) parent.children.link(collection)
if element.IsDecomposedBy: if element.IsDecomposedBy:
@@ -1432,7 +1421,7 @@ class IfcImporter:
aggregates = {} aggregates = {}
for rel_aggregate in rel_aggregates: for rel_aggregate in rel_aggregates:
element = rel_aggregate.RelatingObject 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} aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection self.collections[element.GlobalId] = collection
@@ -1663,9 +1652,6 @@ class IfcImporter:
else: else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"]) 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): def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items(): for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object): if isinstance(obj, bpy.types.Object):
@@ -1813,16 +1799,6 @@ class IfcImporter:
): ):
return representation.Items[0].MappingTarget 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): def create_camera(self, element, shape):
if hasattr(shape, "geometry"): if hasattr(shape, "geometry"):
geometry = shape.geometry geometry = shape.geometry
@@ -1837,7 +1813,7 @@ class IfcImporter:
height = max(y) - min(y) height = max(y) - min(y)
depth = max(z) - min(z) 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.type = "ORTHO"
camera.ortho_scale = width if width > height else height camera.ortho_scale = width if width > height else height
camera.clip_end = depth camera.clip_end = depth
@@ -1877,7 +1853,7 @@ class IfcImporter:
else: else:
geometry = shape 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.dimensions = "3D"
curve.resolution_u = 2 curve.resolution_u = 2
@@ -1903,7 +1879,7 @@ class IfcImporter:
else: else:
geometry = shape 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 props = bpy.context.scene.BIMGeoreferenceProperties
if ( if (
@@ -2018,17 +1994,6 @@ class IfcImporter:
self.added_data[element.id()] = obj self.added_data[element.id()] = obj
IfcStore.link_element(element, 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): def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj) 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_idname = "bim.update_representation"
bl_label = "Update Representation" bl_label = "Update Representation"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty() obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty() ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
if context.active_object and context.active_object.mode != "OBJECT": if context.active_object and context.active_object.mode != "OBJECT":
# Ensure mode is object to prevent invalid mesh data causing CTD # 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) group = drawing_tool.get_drawing_group(drawing)
if drawing_tool.get_name(group) != name: if drawing_tool.get_name(group) != name:
ifc.run("attribute.edit_attributes", product=group, attributes={"Name": 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): 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_delete_existing_annotation = False
should_create_annotation = False should_create_annotation = False
if annotation and ( if annotation:
not reference_obj if ifc.is_deleted(annotation):
or ifc.is_moved(reference_obj) should_delete_existing_annotation = True
or ifc.is_edited(reference_obj) elif reference_obj and (ifc.is_moved(reference_obj) or ifc.is_edited(reference_obj)):
or ifc.is_deleted(reference_element) should_delete_existing_annotation = True
or ifc.is_deleted(annotation) elif not reference_obj and ifc.is_deleted(reference_element):
): should_delete_existing_annotation = True
should_delete_existing_annotation = True
if reference_obj and (should_delete_existing_annotation or not annotation): if reference_obj and (should_delete_existing_annotation or not annotation):
should_create_annotation = True should_create_annotation = True
@@ -279,5 +280,6 @@ def select_assigned_product(drawing):
def activate_drawing_view(ifc, drawing_tool, drawing): def activate_drawing_view(ifc, drawing_tool, drawing):
camera = ifc.get_object(drawing) camera = ifc.get_object(drawing)
if not camera: 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) drawing_tool.activate_view(camera)
+5
View File
@@ -308,6 +308,11 @@ class Library:
def set_editing_mode(cls, mode): pass def set_editing_mode(cls, mode): pass
@interface
class Loader:
pass
@interface @interface
class Material: class Material:
def add_default_material_object(cls): pass 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.georeference import Georeference
from blenderbim.tool.ifc import Ifc from blenderbim.tool.ifc import Ifc
from blenderbim.tool.library import Library from blenderbim.tool.library import Library
from blenderbim.tool.loader import Loader
from blenderbim.tool.material import Material from blenderbim.tool.material import Material
from blenderbim.tool.misc import Misc from blenderbim.tool.misc import Misc
from blenderbim.tool.model import Model from blenderbim.tool.model import Model
+87 -4
View File
@@ -21,6 +21,7 @@ import re
import bpy import bpy
import math import math
import bmesh import bmesh
import logging
import mathutils import mathutils
import webbrowser import webbrowser
import subprocess 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.scheduler as scheduler
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.prop import get_diagram_scales
class Drawing(blenderbim.core.tool.Drawing): class Drawing(blenderbim.core.tool.Drawing):
@@ -204,7 +206,8 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod @classmethod
def get_drawing_collection(cls, drawing): def get_drawing_collection(cls, drawing):
obj = tool.Ifc.get_object(drawing) obj = tool.Ifc.get_object(drawing)
return obj.users_collection[0] if obj:
return obj.users_collection[0]
@classmethod @classmethod
def get_drawing_group(cls, drawing): def get_drawing_group(cls, drawing):
@@ -296,6 +299,83 @@ class Drawing(blenderbim.core.tool.Drawing):
if rel.is_a("IfcRelAssignsToProduct"): if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct 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 @classmethod
def import_drawings(cls): def import_drawings(cls):
bpy.context.scene.DocProperties.drawings.clear() bpy.context.scene.DocProperties.drawings.clear()
@@ -923,9 +1003,12 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod @classmethod
def is_drawing_active(cls): def is_drawing_active(cls):
camera = bpy.context.scene.camera camera = bpy.context.scene.camera
return (camera and camera.type == "CAMERA" and return (
camera.BIMObjectProperties.ifc_definition_id and camera
any(a.type == 'VIEW_3D' for a in bpy.context.screen.areas)) and camera.type == "CAMERA"
and camera.BIMObjectProperties.ifc_definition_id
and any(a.type == "VIEW_3D" for a in bpy.context.screen.areas)
)
@classmethod @classmethod
def is_camera_orthographic(cls): 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(",", ""))