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.ChangeLibraryElement,
operator.CreateClippingPlane, operator.CreateClippingPlane,
operator.CreateProject, operator.CreateProject,
operator.ClearRecentIFCProjects,
operator.DisableCulling, operator.DisableCulling,
operator.DisableEditingHeader, operator.DisableEditingHeader,
operator.EditHeader, operator.EditHeader,
@@ -66,6 +67,7 @@ classes = (
ui.BIM_MT_new_project, ui.BIM_MT_new_project,
ui.BIM_MT_project, ui.BIM_MT_project,
ui.BIM_PT_new_project_wizard, ui.BIM_PT_new_project_wizard,
ui.BIM_MT_recent_projects,
ui.BIM_PT_project, ui.BIM_PT_project,
ui.BIM_PT_project_library, ui.BIM_PT_project_library,
ui.BIM_PT_links, ui.BIM_PT_links,
@@ -24,6 +24,7 @@ import logging
import tempfile import tempfile
import traceback import traceback
import subprocess import subprocess
import datetime
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
@@ -655,6 +656,46 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False) is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", 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) 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): def execute(self, context):
@persistent @persistent
@@ -683,10 +724,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
for obj in bpy.data.objects: for obj in bpy.data.objects:
bpy.data.objects.remove(obj) 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.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement")) context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
tool.Blender.register_toolbar() tool.Blender.register_toolbar()
tool.Blender.add_recent_ifc_project(filepath.absolute())
if not self.is_advanced: if not self.is_advanced:
bpy.ops.bim.load_project_elements() bpy.ops.bim.load_project_elements()
except: except:
@@ -703,6 +747,16 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
IFCFileSelector.draw(self, context) 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): class RevertProject(bpy.types.Operator, IFCFileSelector):
bl_idname = "bim.revert_project" bl_idname = "bim.revert_project"
bl_label = "Revert IFC Project" bl_label = "Revert IFC Project"
@@ -18,6 +18,7 @@
import os import os
import blenderbim.bim import blenderbim.bim
import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search from blenderbim.bim.helper import prop_with_search
from bpy.types import Panel, Menu, UIList from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore 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" 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): class BIM_MT_new_project(Menu):
bl_idname = "BIM_MT_new_project" bl_idname = "BIM_MT_new_project"
bl_label = "New Project" bl_label = "New Project"
@@ -44,6 +67,7 @@ class BIM_MT_new_project(Menu):
self.layout.operator_context = "INVOKE_DEFAULT" self.layout.operator_context = "INVOKE_DEFAULT"
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER") op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True 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? # Do we need to set it back to exec default?
# self.layout.operator_context = "EXEC_DEFAULT" # self.layout.operator_context = "EXEC_DEFAULT"
self.layout.separator() self.layout.separator()
@@ -67,6 +91,7 @@ def file_menu(self, context):
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW") self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER") op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True op.should_start_fresh_session = True
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
self.layout.separator() self.layout.separator()
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project") op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False op.should_save_as = False
+63
View File
@@ -19,6 +19,7 @@
import bpy import bpy
import bmesh import bmesh
import json import json
import os
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
import blenderbim.core.tool import blenderbim.core.tool
@@ -1069,3 +1070,65 @@ class Blender(blenderbim.core.tool.Blender):
bpy.utils.unregister_class(panel) bpy.utils.unregister_class(panel)
bpy.utils.register_class(panel) bpy.utils.register_class(panel)
del polls[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([])