Open recent IFC project #3657

Example - https://imgur.com/a/EDfBTJS

Paths to the opened IFC projects are now stored and then you can open them from File -> Open Recent IFC Project menu. It's done in very similar way to how Blender does it, list of files is stored in `Blender\4.1\config\recent-ifc-projects.txt`.
This commit is contained in:
Andrej730
2024-06-28 15:33:34 +05:00
parent 0e2cc463c8
commit d4b055aeb2
4 changed files with 145 additions and 1 deletions
@@ -30,6 +30,7 @@ classes = (
operator.ChangeLibraryElement,
operator.CreateClippingPlane,
operator.CreateProject,
operator.ClearRecentIFCProjects,
operator.DisableCulling,
operator.DisableEditingHeader,
operator.EditHeader,
@@ -66,6 +67,7 @@ classes = (
ui.BIM_MT_new_project,
ui.BIM_MT_project,
ui.BIM_PT_new_project_wizard,
ui.BIM_MT_recent_projects,
ui.BIM_PT_project,
ui.BIM_PT_project_library,
ui.BIM_PT_links,
@@ -24,6 +24,7 @@ import logging
import tempfile
import traceback
import subprocess
import datetime
import numpy as np
import ifcopenshell
import ifcopenshell.api
@@ -655,6 +656,46 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
should_start_fresh_session: bpy.props.BoolProperty(name="Should Start Fresh Session", default=True)
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
@classmethod
def description(cls, context, properties):
tooltip = cls.bl_description
if not properties.use_detailed_tooltip:
return tooltip
filepath = properties.filepath
if not filepath:
return tooltip
filepath = Path(filepath)
tooltip += f".\n\n"
if not filepath.exists():
tooltip += "File does not exist"
return tooltip
def get_modified_date(st_mtime: float) -> str:
mod_time = datetime.datetime.fromtimestamp(st_mtime)
today = datetime.date.today()
if mod_time.date() == today:
return f"Today {mod_time.strftime('%I:%M %p')}"
elif mod_time.date() == today - datetime.timedelta(days=1):
return f"Yesterday {mod_time.strftime('%I:%M %p')}"
return mod_time.strftime("%d %b %Y")
def get_file_size(size_bytes: float) -> str:
if size_bytes < 1024 * 1024: # Less than 1 MiB
size = size_bytes / 1024
return f"{size:.0f} KiB"
else:
size = size_bytes / (1024 * 1024)
return f"{size:.1f} MiB"
file_stat = filepath.stat()
tooltip += f"Modified: {get_modified_date(file_stat.st_mtime)}"
tooltip += f"\nSize: {get_file_size(file_stat.st_size)}"
return tooltip
def execute(self, context):
@persistent
@@ -683,10 +724,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
context.scene.BIMProperties.ifc_file = self.get_filepath()
filepath = Path(self.get_filepath())
context.scene.BIMProperties.ifc_file = str(filepath)
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
tool.Blender.register_toolbar()
tool.Blender.add_recent_ifc_project(filepath.absolute())
if not self.is_advanced:
bpy.ops.bim.load_project_elements()
except:
@@ -703,6 +747,16 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
IFCFileSelector.draw(self, context)
class ClearRecentIFCProjects(bpy.types.Operator):
bl_idname = "bim.clear_recent_ifc_projects"
bl_label = "Clear Recent IFC Projects List"
bl_options = {"REGISTER"}
def execute(self, context):
tool.Blender.clear_recent_ifc_projects()
return {"FINISHED"}
class RevertProject(bpy.types.Operator, IFCFileSelector):
bl_idname = "bim.revert_project"
bl_label = "Revert IFC Project"
@@ -18,6 +18,7 @@
import os
import blenderbim.bim
import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search
from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore
@@ -36,6 +37,28 @@ class BIM_MT_project(Menu):
self.layout.operator("bim.new_project", text="New Project Wizard").preset = "wizard"
class BIM_MT_recent_projects(Menu):
bl_idname = "BIM_MT_recent_projects"
bl_label = "Open Recent IFC Project"
def draw(self, context):
paths = tool.Blender.get_recent_ifc_projects()
if not paths:
self.layout.label(text="No Recent IFC Projects")
return
for path in paths:
op = self.layout.operator(
"bim.load_project", text=path.name, icon_value=blenderbim.bim.icons["IFC"].icon_id
)
op.filepath = str(path)
op.should_start_fresh_session = True
op.use_detailed_tooltip = True
self.layout.separator()
self.layout.operator("bim.clear_recent_ifc_projects", icon="TRASH")
class BIM_MT_new_project(Menu):
bl_idname = "BIM_MT_new_project"
bl_label = "New Project"
@@ -44,6 +67,7 @@ class BIM_MT_new_project(Menu):
self.layout.operator_context = "INVOKE_DEFAULT"
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
# Do we need to set it back to exec default?
# self.layout.operator_context = "EXEC_DEFAULT"
self.layout.separator()
@@ -67,6 +91,7 @@ def file_menu(self, context):
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
self.layout.separator()
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False
+63
View File
@@ -19,6 +19,7 @@
import bpy
import bmesh
import json
import os
import ifcopenshell.api
import ifcopenshell.util.element
import blenderbim.core.tool
@@ -1069,3 +1070,65 @@ class Blender(blenderbim.core.tool.Blender):
bpy.utils.unregister_class(panel)
bpy.utils.register_class(panel)
del polls[panel]
@classmethod
def get_recent_ifc_projects_path(cls) -> Path:
return Path(bpy.utils.user_resource("CONFIG")) / "recent-ifc-projects.txt"
_recent_ifc_projects_loaded: bool = False
_recent_ifc_projects: list[Path] = []
@classmethod
def get_recent_ifc_projects(cls) -> list[Path]:
if cls._recent_ifc_projects_loaded:
return cls._recent_ifc_projects
filepath = cls.get_recent_ifc_projects_path()
if not filepath.exists():
cls._recent_ifc_projects = []
return []
paths = []
with open(filepath, "r") as fi:
for line in fi:
line = line.strip()
if not line:
continue
paths.append(Path(line))
cls._recent_ifc_projects = paths
return paths
@classmethod
def write_recent_ifc_projects(cls, filepaths: list[Path]) -> None:
recent_projects_path = cls.get_recent_ifc_projects_path()
try:
recent_projects_path.parent.mkdir(parents=True, exist_ok=True)
with open(recent_projects_path, "w") as fo:
fo.write("\n".join(str(p) for p in filepaths))
cls._recent_ifc_projects_loaded = False
except PermissionError:
msg = (
f"WARNING. PermissionError trying to access '{str(recent_projects_path)}'. "
"List of recently opened IFC projects won't be stored between Blender sessions."
)
print(msg)
cls._recent_ifc_projects = filepaths
@classmethod
def add_recent_ifc_project(cls, filepath: Path) -> None:
"""Add `filepath` to the list of the recently opened IFC projects.
If `filepath` was opened before, bump it in the list.
"""
current_filepaths = cls.get_recent_ifc_projects()
if filepath in current_filepaths:
current_filepaths.remove(filepath)
current_filepaths = [filepath] + current_filepaths
# Limit it to 20 recent files.
current_filepaths = current_filepaths[:20]
cls.write_recent_ifc_projects(current_filepaths)
@classmethod
def clear_recent_ifc_projects(cls) -> None:
cls.write_recent_ifc_projects([])