Backspace everything regarding HDF5

This commit is contained in:
Thomas Krijnen
2026-05-08 16:20:26 +02:00
parent bd436765bf
commit 554c7174e3
66 changed files with 50 additions and 1928 deletions
-1
View File
@@ -1 +0,0 @@
This cache folder contains .h5 files. These files cache IFC geometry for performance only. You may safely clear the contents of this cache folder without losing data.
-1
View File
@@ -43,7 +43,6 @@ class IfcExporter:
def export(self):
self.file = tool.Ifc.get()
self.set_header()
IfcStore.update_cache()
self.sync_all_objects()
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip":
-75
View File
@@ -18,9 +18,7 @@
from __future__ import annotations
import hashlib
import os
import shutil
import tempfile
import traceback
import uuid
@@ -31,7 +29,6 @@ from typing import Literal, NotRequired, Optional, TypedDict, Union
import bpy
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper
from ifcopenshell.file import UndoSystemError
@@ -72,8 +69,6 @@ class IfcStore:
"""Should be set only using ``tool.Ifc.set``."""
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
cache_path: Optional[str] = None
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
edited_objs: set[bpy.types.Object] = set()
@@ -95,8 +90,6 @@ class IfcStore:
IfcStore.path = ""
IfcStore.file = None
IfcStore.schema = None
IfcStore.cache = None
IfcStore.cache_path = None
IfcStore.id_map = {}
IfcStore.guid_map = {}
IfcStore.edited_objs = set()
@@ -130,74 +123,6 @@ class IfcStore:
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
@staticmethod
def generate_cache_path() -> str:
"""Generate cache path based on the active file and it's path."""
assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
prefs = tool.Blender.get_addon_preferences()
cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
return cache_path
@staticmethod
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
"""Get existing cache for the current file or create a new one.
.h5 cache name reflects IFC filepath and it's current header's timestamp.
"""
if IfcStore.cache is None and IfcStore.path:
cache_path = IfcStore.generate_cache_path()
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
IfcStore.cache_path = cache_path
cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
cache_preexists = cache_path.exists()
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
IfcStore.cache_path, cache_settings, serializer_settings
)
if cache_preexists:
print(f"Successfully loaded existing cache: {cache_path.name}.")
else:
print("New cache was created.")
except Exception as e:
if cache_preexists:
print(f"Failed to create a cache from existing file '{cache_path.name}': {str(e)}.")
else:
print(f"Failed to create a cache: {str(e)}.")
# No point to trying again the same operation.
return
os.remove(IfcStore.cache_path)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
IfcStore.cache_path, cache_settings, serializer_settings
)
print("New cache was created.")
except Exception as e:
print(f"Failed to create a cache: {str(e)}.")
return
return IfcStore.cache
@staticmethod
def update_cache() -> None:
"""Update cache filename after timestamp was updated."""
if not IfcStore.cache:
return
assert IfcStore.cache_path
new_cache_path = IfcStore.generate_cache_path()
IfcStore.cache = None
try:
shutil.move(IfcStore.cache_path, new_cache_path)
except PermissionError:
try:
shutil.copy2(IfcStore.cache_path, new_cache_path)
except PermissionError:
pass # Well we tried. No cache for you!
IfcStore.get_cache()
@staticmethod
def load_file(path: str) -> None:
if not os.path.isfile(path):
-7
View File
@@ -721,10 +721,6 @@ class IfcImporter:
iterator = ifcopenshell.geom.iterator(
settings, self.file, include=products, geometry_library=self.ifc_import_settings.geometry_library
)
if self.ifc_import_settings.should_cache:
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
@@ -1267,7 +1263,6 @@ class IfcImportSettings:
self.should_merge_materials_by_colour = False
self.should_load_geometry = True
self.should_clean_mesh = False
self.should_cache = True
self.deflection_tolerance = 0.05 # Default is 0.001, but I find this to be more practical
self.angular_tolerance = 0.5
self.void_limit = 30
@@ -1295,7 +1290,6 @@ class IfcImportSettings:
context=None, input_file: Optional[str] = None, logger: Optional[logging.Logger] = None
) -> IfcImportSettings:
scene_diff = tool.Blender.get_diff_props()
prefs = tool.Blender.get_addon_preferences()
props = tool.Project.get_project_props()
settings = IfcImportSettings()
settings.input_file = input_file
@@ -1308,7 +1302,6 @@ class IfcImportSettings:
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
settings.should_load_geometry = props.should_load_geometry
settings.should_clean_mesh = props.should_clean_mesh
settings.should_cache = prefs.should_always_cache or props.should_cache
settings.deflection_tolerance = props.deflection_tolerance
settings.angular_tolerance = props.angular_tolerance
settings.void_limit = props.void_limit
@@ -37,7 +37,6 @@ classes = (
operator.PrintObjectPlacement,
operator.PrintUnusedElementStats,
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeUnusedElementsByClass,
operator.PurgeUnusedObjects,
operator.RestartBlender,
@@ -570,17 +570,6 @@ class SelectExpressFile(bpy.types.Operator, ImportHelper):
return {"FINISHED"}
class PurgeHdf5Cache(bpy.types.Operator):
bl_idname = "bim.purge_hdf5_cache"
bl_label = "Purge HDF5 Cache"
bl_description = "Clean up HDF5 cache files except the ones that currently loaded"
def execute(self, context):
core.purge_hdf5_cache(tool.Debug)
self.report({"INFO"}, "HDF5 cache purged.")
return {"FINISHED"}
class OverrideDisplayType(bpy.types.Operator):
bl_idname = "bim.override_display_type"
bl_label = "Override Display Type"
-3
View File
@@ -64,9 +64,6 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.copy_debug_information")
row = layout.row()
row.operator("bim.purge_hdf5_cache")
row = layout.row()
row.operator("bim.update_representation", text="Manually Save Representation")
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import hashlib
import json
import logging
import multiprocessing
@@ -901,10 +900,7 @@ class CreateDrawing(bpy.types.Operator):
# All very hackish whilst prototyping
exporter = bonsai.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_elements = exporter.sync_all_objects()
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
if cache := IfcStore.get_cache():
[cache.remove(guid) for guid in invalidated_guids]
exporter.sync_all_objects()
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
@@ -922,7 +918,6 @@ class CreateDrawing(bpy.types.Operator):
cached_linework -= edited_guids
bim_props = tool.Blender.get_bim_props()
prefs = tool.Blender.get_addon_preferences()
files = {bim_props.ifc_file: tool.Ifc.get()}
props = tool.Project.get_project_props()
@@ -935,12 +930,8 @@ class CreateDrawing(bpy.types.Operator):
tree = ifcopenshell.geom.tree()
tree.enable_face_styles(True)
for ifc_path, ifc in files.items():
for ifc in files.values():
# Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
self.serialiser.setFile(ifc)
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
@@ -576,9 +576,6 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, f"Object '{obj.name}' has openings - representation cannot be updated.")
return
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
if product.is_a("IfcGridAxis"):
# Grid geometry does not follow the "representation" paradigm and needs to be treated specially
tool.Model.create_axis_curve(obj, product)
@@ -1488,7 +1488,6 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
def link_ifc(self) -> Union[set[str], None]:
blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend")
h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5")
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
def should_clear_cache() -> bool:
@@ -345,14 +345,6 @@ class BIMProjectProperties(PropertyGroup):
),
default=False,
)
should_cache: BoolProperty(
name="Cache",
description=(
"Cache loaded geometry to .h5 file in your cache directory (see in preferences) "
"for faster imports and geometry reloads."
),
default=False,
)
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.05)
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
void_limit: IntProperty(
@@ -521,7 +513,6 @@ class BIMProjectProperties(PropertyGroup):
should_merge_materials_by_colour: bool
should_load_geometry: bool
should_clean_mesh: bool
should_cache: bool
deflection_tolerance: float
angular_tolerance: float
void_limit: int
@@ -207,8 +207,6 @@ class BIM_PT_project(Panel):
row = self.layout.row()
row.prop(pprops, "should_clean_mesh")
row = self.layout.row()
row.prop(pprops, "should_cache")
row = self.layout.row()
row.prop(pprops, "should_load_geometry")
row = self.layout.row()
row.prop(pprops, "should_merge_materials_by_colour")
@@ -207,7 +207,6 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
representation=representation,
)
tool.Geometry.unlock_scale_object_with_openings(obj)
tool.Geometry.clear_cache(element)
return {"FINISHED"}
-6
View File
@@ -665,10 +665,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
should_always_cache: BoolProperty(
name="Always Cache Geometry",
description="Whether to always cache geometry regardless of 'Cache' setting during Advanced Project Load.",
)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
@@ -777,7 +773,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bsdd_baseurl: str
should_disable_undo_on_save: bool
should_stream: bool
should_always_cache: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
gizmos: GizmoPreferences
@@ -976,7 +971,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "opening_focus_opacity")
layout.prop(self, "should_disable_undo_on_save")
layout.prop(self, "should_stream")
layout.prop(self, "should_always_cache")
layout.label(text="bSDD:")
layout.prop(self, "bsdd_load_preview_dictionaries")
layout.prop(self, "bsdd_load_inactive_dictionaries")
-4
View File
@@ -30,10 +30,6 @@ def parse_express(debug: type[tool.Debug], filename: str) -> None:
debug.add_schema_identifier(debug.load_express(filename))
def purge_hdf5_cache(debug: type[tool.Debug]) -> None:
debug.purge_hdf5_cache()
def purge_unused_elements(ifc: type[tool.Ifc], debug: type[tool.Debug], ifc_class: str) -> int:
ifc_file = ifc.get()
unused_elements = [i for i in ifc_file.by_type(ifc_class) if ifc_file.get_total_inverses(i) == 0]
-2
View File
@@ -44,7 +44,6 @@ def edit_object_placement(
element = ifc.get_entity(obj)
if not element:
return
geometry.clear_cache(element)
if apply_scale:
geometry.clear_scale(obj)
geometry.get_blender_offset_type(obj)
@@ -125,7 +124,6 @@ def switch_representation(
element = ifc.get_entity(obj)
assert element
geometry.clear_cache(element)
geometry.reimport_element_representations(obj, representation, apply_openings=apply_openings)
-2
View File
@@ -283,7 +283,6 @@ class Cost:
class Debug:
def add_schema_identifier(cls, schema): pass
def load_express(cls, filename): pass
def purge_hdf5_cache(cls): pass
@interface
@@ -422,7 +421,6 @@ class Feature:
@interface
class Geometry:
def change_object_data(cls, obj, data, is_global=False): pass
def clear_cache(cls, element): pass
def clear_modifiers(cls, obj): pass
def clear_scale(cls, obj): pass
def copy_data_links(cls, data, copied_entities) -> None: pass
-11
View File
@@ -59,17 +59,6 @@ class Debug(bonsai.core.tool.Debug):
ifcopenshell.register_schema(schema)
return schema.schema
@classmethod
def purge_hdf5_cache(cls) -> None:
prefs = tool.Blender.get_addon_preferences()
cache_dir = prefs.cache_dir
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
for f in filelist:
try:
os.remove(os.path.join(cache_dir, f))
except PermissionError:
pass
@classmethod
def debug_bmesh(cls, bm: bmesh.types.BMesh, name: str = "Debug") -> bpy.types.Object:
mesh = bpy.data.meshes.new("Debug")
-12
View File
@@ -73,7 +73,6 @@ import bonsai.core.style
import bonsai.core.system
import bonsai.core.tool
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
if TYPE_CHECKING:
from bonsai.bim.module.geometry.prop import (
@@ -109,16 +108,6 @@ class Geometry(bonsai.core.tool.Geometry):
raise Exception("user_remap is not supported for meshes in EDIT mode")
old_data.user_remap(new_data)
@classmethod
def get_cache(cls) -> Union[ifcopenshell.geom.serializers.hdf5, None]:
return IfcStore.get_cache()
@classmethod
def clear_cache(cls, element: ifcopenshell.entity_instance) -> None:
cache = IfcStore.get_cache()
if cache and hasattr(element, "GlobalId"):
cache.remove(element.GlobalId)
@classmethod
def clear_modifiers(cls, obj: bpy.types.Object) -> None:
for modifier in obj.modifiers:
@@ -818,7 +807,6 @@ class Geometry(bonsai.core.tool.Geometry):
if not cls.has_data_users(old_data):
cls.delete_data(old_data)
cls.clear_modifiers(obj)
cls.clear_cache(element)
# Import swept disk solids as Blender curves if possible.
elements_without_openings = {e for e in elements if not getattr(e, "HasOpenings", False)}
-3
View File
@@ -22,9 +22,6 @@ props = tool.Georeference.get_georeference_props()
props = tool.Project.get_project_props()
# Generally recommended to disable caching for stability right now
props.should_cache = False
# If you are not authoring, it is recommended to enable this.
# When enabled, types, openings, and non geometric elements are not loaded.
props.is_coordinating = True
@@ -9,7 +9,6 @@ class BlenderImporter:
def __init__(self):
self.file = ifcopenshell.open("/home/dion/untitled.ifc")
self.cache_path = "cache.h5"
self.should_use_cpu_multiprocessing = True
self.deflection_tolerance = 0.001
self.angular_tolerance = 0.5
@@ -82,9 +81,6 @@ class BlenderImporter:
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=products)
else:
iterator = ifcopenshell.geom.iterator(settings, self.file, include=products)
cache = self.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
@@ -113,13 +109,4 @@ class BlenderImporter:
print("Done creating geometry")
return results
def get_cache(self):
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
try:
return ifcopenshell.geom.serializers.hdf5(self.cache_path, cache_settings, serializer_settings)
except:
return
BlenderImporter().execute()
-6
View File
@@ -25,9 +25,3 @@ class TestParseExpress:
debug.load_express("filename").should_be_called().will_return("schema")
debug.add_schema_identifier("schema").should_be_called()
subject.parse_express(debug, "filename")
class TestPurgeHdf5Cache:
def test_run(self, debug):
debug.purge_hdf5_cache().should_be_called()
subject.purge_hdf5_cache(debug)
-2
View File
@@ -24,7 +24,6 @@ from test.core.bootstrap import geometry, ifc, style, surveyor
class TestEditObjectPlacement:
def predict(self, ifc, geometry, surveyor):
ifc.get_entity("obj").should_be_called().will_return("element")
geometry.clear_cache("element").should_be_called()
geometry.clear_scale("obj").should_be_called()
geometry.get_blender_offset_type("obj").should_be_called()
surveyor.get_absolute_matrix("obj").should_be_called().will_return("matrix")
@@ -197,7 +196,6 @@ class TestSwitchRepresentation:
def test_switching_to_a_representation(self, ifc, geometry):
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
ifc.get_entity("obj").should_be_called().will_return("element")
geometry.clear_cache("element").should_be_called()
geometry.reimport_element_representations("obj", "mapped_rep", apply_openings=True).should_be_called()
subject.switch_representation(
ifc,
-20
View File
@@ -17,8 +17,6 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import os
from pathlib import Path
import bpy
import ifcopenshell
import ifcopenshell.api.style
@@ -55,24 +53,6 @@ class TestLoadExpress(NewFile):
os.remove(schema_path + ".cache.dat")
class TestPurgeHdf5Cache(NewFile):
def test_run(self):
prefs = tool.Blender.get_addon_preferences()
cache_dir = Path(prefs.cache_dir)
test_file = cache_dir / "test.h5"
test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.touch()
# Ensure it can skip currently loaded cache.
loaded_file_path = test_file.with_stem("test_loaded")
loaded_file = open(loaded_file_path, "w")
subject.purge_hdf5_cache()
# On Unix loaded files are not locked.
paths = [loaded_file_path] if os.name == "nt" else []
assert [f for f in cache_dir.iterdir() if f.suffix == ".h5"] == paths
class TestMergeIdenticalObject(NewFile):
def test_merge_identical_styles(self):
tool.Ifc.set(ifc := ifcopenshell.file())