mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Type properties are now shown and direct linking of IFC (background blend generation) is now possible
This commit is contained in:
@@ -53,7 +53,7 @@ classes = (
|
||||
operator.zzz,
|
||||
operator.zxc,
|
||||
operator.aaa,
|
||||
operator.asdfasdf,
|
||||
operator.LoadLinkedProject,
|
||||
operator.QueryLinkedElement,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 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 gpu
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
class ProjectDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
|
||||
try:
|
||||
obj = context.scene.BIMProjectProperties.queried_obj
|
||||
selected_vertices = obj["selected_vertices"]
|
||||
selected_edges = obj["selected_edges"]
|
||||
selected_tris = obj["selected_tris"]
|
||||
except:
|
||||
return
|
||||
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
@@ -814,16 +815,17 @@ class ToggleFilterCategories(bpy.types.Operator):
|
||||
|
||||
class LinkIfc(bpy.types.Operator):
|
||||
bl_idname = "bim.link_ifc"
|
||||
bl_label = "Link Blend/IFC File"
|
||||
bl_label = "Link IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "This will link the Blender file that is synced with the IFC file"
|
||||
bl_description = "Reference in a read-only IFC model in the background"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
start = time.time()
|
||||
files = [f.name for f in self.files] if self.files else [self.filepath]
|
||||
for filename in files:
|
||||
filepath = os.path.join(self.directory, filename)
|
||||
@@ -835,6 +837,7 @@ class LinkIfc(bpy.types.Operator):
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=filepath)
|
||||
print(f"Finished linking {len(files)} IFCs", time.time() - start)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -890,6 +893,13 @@ class LoadLink(bpy.types.Operator):
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
if self.filepath.lower().endswith(".blend"):
|
||||
self.link_blend(filepath)
|
||||
elif self.filepath.lower().endswith(".ifc"):
|
||||
self.link_ifc()
|
||||
return {"FINISHED"}
|
||||
|
||||
def link_blend(self, filepath):
|
||||
with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
|
||||
data_to.scenes = data_from.scenes
|
||||
for scene in bpy.data.scenes:
|
||||
@@ -899,9 +909,36 @@ class LoadLink(bpy.types.Operator):
|
||||
if "IfcProject" not in child.name:
|
||||
continue
|
||||
bpy.data.scenes[0].collection.children.link(child)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link = bpy.context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def link_ifc(self):
|
||||
blend_filepath = self.filepath + ".cache.blend"
|
||||
h5_filepath = self.filepath + ".cache.h5"
|
||||
|
||||
if not os.path.exists(blend_filepath):
|
||||
code = f"""
|
||||
import bpy
|
||||
|
||||
def run():
|
||||
bpy.ops.bim.load_linked_project(filepath="{self.filepath}")
|
||||
bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath}")
|
||||
|
||||
try:
|
||||
run()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
|
||||
temp_file.write(code)
|
||||
run = subprocess.run([bpy.app.binary_path, "-b", "--python", temp_file.name, "--python-exit-code", "1"])
|
||||
if run.returncode == 1:
|
||||
print("An error occurred while processing your IFC.")
|
||||
|
||||
self.link_blend(blend_filepath)
|
||||
|
||||
|
||||
class ReloadLink(bpy.types.Operator):
|
||||
@@ -1143,7 +1180,6 @@ class xxx(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
import time
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import numpy as np
|
||||
from mathutils import Matrix
|
||||
@@ -1540,83 +1576,99 @@ class aaa(bpy.types.Operator):
|
||||
return obj
|
||||
|
||||
|
||||
class asdfasdf(bpy.types.Operator):
|
||||
bl_idname = "bim.asdfasdf"
|
||||
bl_label = "Regular IFC iterator with C++ chunking"
|
||||
class LoadLinkedProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_linked_project"
|
||||
bl_label = "Load a project for viewing only."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
import time
|
||||
import ifcpatch
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import numpy as np
|
||||
from mathutils import Matrix
|
||||
import resource
|
||||
|
||||
mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000
|
||||
start = time.time()
|
||||
|
||||
self.collection = bpy.data.collections.new("Project")
|
||||
# ifc_file = ifcopenshell.open('/home/dion/test.ifc')
|
||||
ifc_file = ifcopenshell.open("/home/dion/drive/ifcs/racbasicsampleproject.ifc")
|
||||
# ifc_file = ifcopenshell.open('/home/dion/drive/ifcs/TXG_sample_project-fixed-IFC4.ifc')
|
||||
# ifc_file = ifcopenshell.open("/home/dion/tmp/petrubug/F-ELECT.ifc")
|
||||
self.filepath = self.filepath or "/home/dion/drive/ifcs/racbasicsampleproject.ifc"
|
||||
|
||||
self.collection = bpy.data.collections.new("IfcProject/" + os.path.basename(self.filepath))
|
||||
self.file = ifcopenshell.open(self.filepath)
|
||||
# self.file = ifcopenshell.open('/home/dion/test.ifc')
|
||||
# self.file = ifcopenshell.open('/home/dion/drive/ifcs/TXG_sample_project-fixed-IFC4.ifc')
|
||||
# self.file = ifcopenshell.open("/home/dion/tmp/petrubug/F-ELECT.ifc")
|
||||
print("Finished opening")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
# settings.set_context_ids([ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW").id()])
|
||||
els = set(ifc_file.by_type("IfcElement"))
|
||||
els |= set(ifc_file.by_type("IfcSite"))
|
||||
els -= set(ifc_file.by_type("IfcFeatureElement"))
|
||||
els = list(els)
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=els)
|
||||
meshes = {}
|
||||
blender_mats = {}
|
||||
self.db_filepath = self.filepath + ".cache.sqlite"
|
||||
db = ifcpatch.execute(
|
||||
{"input": self.filepath, "file": self.file, "recipe": "ExtractPropertiesToSQLite", "arguments": []}
|
||||
)
|
||||
ifcpatch.write(db, self.db_filepath)
|
||||
print("Finished writing property database")
|
||||
|
||||
total_materials = 0
|
||||
self.materials = []
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
ifc_importer.process_context_filter()
|
||||
|
||||
ci = 0
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
has_processed_chunk = False
|
||||
if iterator.process_chunk():
|
||||
has_processed_chunk = True
|
||||
ci += 1
|
||||
print("Doing chunk", ci)
|
||||
e = iterator.get_chunk()
|
||||
self.elements = set(self.file.by_type("IfcElement"))
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
self.elements |= set(self.file.by_type("IfcProxy"))
|
||||
self.elements |= set(self.file.by_type("IfcSite"))
|
||||
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
||||
self.elements = list(self.elements)
|
||||
|
||||
for c in e.colours:
|
||||
blender_mat = bpy.data.materials.new(str(total_materials))
|
||||
blender_mat.diffuse_color = list(c)
|
||||
self.materials.append(blender_mat)
|
||||
total_materials += 1
|
||||
o = self.create_object(e.get_verts(), e.get_faces(), e.get_materials(), e.get_material_ids())
|
||||
o["guids"] = list(e.guids)
|
||||
o["guid_ids"] = list(e.guid_ids)
|
||||
if not iterator.next():
|
||||
if not has_processed_chunk:
|
||||
# The left over chunk
|
||||
e = iterator.get_chunk()
|
||||
for c in e.colours:
|
||||
for settings in ifc_importer.context_settings:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, self.file, multiprocessing.cpu_count(), include=self.elements
|
||||
)
|
||||
meshes = {}
|
||||
blender_mats = {}
|
||||
|
||||
total_materials = 0
|
||||
self.materials = []
|
||||
|
||||
ci = 0
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
has_processed_chunk = False
|
||||
if iterator.process_chunk():
|
||||
has_processed_chunk = True
|
||||
ci += 1
|
||||
if ci % 50 == 0:
|
||||
print("Doing chunk", ci)
|
||||
chunk = iterator.get_chunk()
|
||||
|
||||
for colour in chunk.colours:
|
||||
blender_mat = bpy.data.materials.new(str(total_materials))
|
||||
blender_mat.diffuse_color = list(c)
|
||||
blender_mat.diffuse_color = list(colour)
|
||||
self.materials.append(blender_mat)
|
||||
total_materials += 1
|
||||
self.create_object(chunk)
|
||||
|
||||
o = self.create_object(e.get_verts(), e.get_faces(), e.get_materials(), e.get_material_ids())
|
||||
o["guids"] = list(e.guids)
|
||||
o["guid_ids"] = list(e.guid_ids)
|
||||
break
|
||||
if not iterator.next():
|
||||
if not has_processed_chunk:
|
||||
# The left over chunk
|
||||
chunk = iterator.get_chunk()
|
||||
for colour in chunk.colours:
|
||||
blender_mat = bpy.data.materials.new(str(total_materials))
|
||||
blender_mat.diffuse_color = list(colour)
|
||||
self.materials.append(blender_mat)
|
||||
total_materials += 1
|
||||
self.create_object(chunk)
|
||||
break
|
||||
|
||||
bpy.context.scene.collection.children.link(self.collection)
|
||||
print("Finished", time.time() - start)
|
||||
newmem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000
|
||||
print("Mem", newmem - mem)
|
||||
bpy.context.scene.collection.children.link(self.collection)
|
||||
print("Finished", time.time() - start)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_object(self, verts, faces, materials, material_ids):
|
||||
def create_object(self, chunk):
|
||||
verts = chunk.get_verts()
|
||||
faces = chunk.get_faces()
|
||||
materials = chunk.get_materials()
|
||||
material_ids = chunk.get_material_ids()
|
||||
|
||||
num_vertices = len(verts) // 3
|
||||
if not num_vertices:
|
||||
return
|
||||
@@ -1645,9 +1697,12 @@ class asdfasdf(bpy.types.Operator):
|
||||
|
||||
mesh.update()
|
||||
|
||||
obj = bpy.data.objects.new("Blah", mesh)
|
||||
obj = bpy.data.objects.new("Chunk", mesh)
|
||||
obj["guids"] = list(chunk.guids)
|
||||
obj["guid_ids"] = list(chunk.guid_ids)
|
||||
obj["db"] = self.db_filepath
|
||||
|
||||
self.collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
|
||||
class QueryLinkedElement(bpy.types.Operator):
|
||||
@@ -1660,10 +1715,20 @@ class QueryLinkedElement(bpy.types.Operator):
|
||||
return context.area.type == "VIEW_3D"
|
||||
|
||||
def execute(self, context):
|
||||
import sqlite3
|
||||
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
|
||||
|
||||
LinksData.linked_data = {}
|
||||
bpy.context.scene.BIMProjectProperties.queried_obj = None
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
region.tag_redraw()
|
||||
elif area.type == "VIEW_3D":
|
||||
area.tag_redraw()
|
||||
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
coord = (self.mouse_x, self.mouse_y)
|
||||
@@ -1704,9 +1769,7 @@ class QueryLinkedElement(bpy.types.Operator):
|
||||
break
|
||||
guid_start_index = guid_end_index
|
||||
|
||||
import sqlite3
|
||||
|
||||
self.db = sqlite3.connect("/home/dion/Projects/ifcopenshell/src/ifcclash/foo.sqlite")
|
||||
self.db = sqlite3.connect(obj["db"])
|
||||
self.c = self.db.cursor()
|
||||
|
||||
self.c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1")
|
||||
@@ -1724,9 +1787,27 @@ class QueryLinkedElement(bpy.types.Operator):
|
||||
for row in rows:
|
||||
properties.setdefault(row[1], {})[row[2]] = row[3]
|
||||
|
||||
sorted_properties = [(k, properties[k]) for k in sorted(properties.keys())]
|
||||
self.c.execute("SELECT * FROM relationships WHERE from_id = ?", (element[0],))
|
||||
relationships = self.c.fetchall()
|
||||
|
||||
LinksData.linked_data = {"attributes": attributes, "properties": sorted_properties}
|
||||
relating_type_id = None
|
||||
|
||||
for relationship in relationships:
|
||||
if relationship[1] == "IfcRelDefinesByType":
|
||||
relating_type_id = relationship[2]
|
||||
|
||||
type_properties = {}
|
||||
if relating_type_id is not None:
|
||||
self.c.execute("SELECT * FROM properties WHERE element_id = ?", (relating_type_id,))
|
||||
rows = self.c.fetchall()
|
||||
for row in rows:
|
||||
type_properties.setdefault(row[1], {})[row[2]] = row[3]
|
||||
|
||||
LinksData.linked_data = {
|
||||
"attributes": attributes,
|
||||
"properties": [(k, properties[k]) for k in sorted(properties.keys())],
|
||||
"type_properties": [(k, type_properties[k]) for k in sorted(type_properties.keys())],
|
||||
}
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
|
||||
@@ -324,6 +324,18 @@ class BIM_PT_links(Panel):
|
||||
row.label(text=name)
|
||||
row.label(text=value)
|
||||
|
||||
if LinksData.linked_data["type_properties"]:
|
||||
row = self.layout.row()
|
||||
row.label(icon="LINKED", text="Type Properties")
|
||||
for pset in LinksData.linked_data["type_properties"]:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=pset[0], icon="COPY_ID")
|
||||
for name, value in pset[1].items():
|
||||
row = box.row(align=True)
|
||||
row.label(text=name)
|
||||
row.label(text=value)
|
||||
|
||||
|
||||
class BIM_UL_library(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 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 bpy
|
||||
from bpy.types import WorkSpaceTool
|
||||
|
||||
|
||||
class QueryTool(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
bl_idname = "bim.query_tool"
|
||||
bl_label = "Query Tool"
|
||||
bl_description = "Fetch data about a linked IFC element"
|
||||
bl_icon = "ops.generic.select_circle"
|
||||
bl_widget = None
|
||||
bl_keymap = (
|
||||
("bim.query_linked_element", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
)
|
||||
@@ -117,8 +117,12 @@ class Patcher:
|
||||
psets = ifcopenshell.util.element.get_psets(element, should_inherit=False)
|
||||
for pset_name, pset_data in psets.items():
|
||||
for prop_name, value in pset_data.items():
|
||||
if prop_name == "id":
|
||||
if prop_name == "id" or value is None or value == "":
|
||||
continue
|
||||
if isinstance(value, bool):
|
||||
value = "True" if value else "False"
|
||||
elif not isinstance(value, str):
|
||||
value = str(value)
|
||||
properties.append([i, pset_name, prop_name, value])
|
||||
relating_type = ifcopenshell.util.element.get_type(element)
|
||||
if relating_type and relating_type != element:
|
||||
|
||||
Reference in New Issue
Block a user