Bonsai offers to install Git and ifcmerge

If Git isn't installed on Windows, the Git panel now offers to install
it from the Microsoft Store using `winget`.

Then if ifcmerge isn't installed on Linux or Windows, the Git panel
offers to install it from copies bundled in the Bonsai ZIP package.

Note that ifcmerge.exe increases the size of the Bonsai ZIP download by
about 7MB.
This commit is contained in:
Bruno Postle
2025-01-12 14:37:06 +00:00
parent 936984dd31
commit 5454a7dd14
6 changed files with 123 additions and 0 deletions
+8
View File
@@ -229,6 +229,14 @@ endif
# Required for Desktop icon and file association
cp -r bonsai/libs/desktop build/bonsai/libs/
# required for three-way git merging
ifeq ($(PLATFORM), win)
cd build/bonsai/libs/desktop && wget https://github.com/brunopostle/ifcmerge/releases/download/2024-06-24/ifcmerge.zip
cd build/bonsai/libs/desktop && unzip ifcmerge.zip && rm ifcmerge.zip
else
cd build/bonsai/libs/desktop && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge
endif
# Generate translations module for Bonsai build
git clone https://github.com/IfcOpenShell/bonsai-translations.git build/working
$(PYTHON) scripts/bonsai_translations.py -i "build/working" -o "build/bonsai"
@@ -37,6 +37,8 @@ classes = (
operator.Push,
operator.RefreshGit,
operator.SwitchRevision,
operator.InstallGit,
operator.InstallIfcmerge,
prop.IfcGitTag,
prop.IfcGitListItem,
prop.IfcGitProperties,
@@ -386,3 +386,36 @@ class ObjectLog(bpy.types.Operator):
step_id = context.active_object.BIMObjectProperties.ifc_definition_id
core.entity_log(tool.IfcGit, tool.Ifc, step_id, self)
return {"FINISHED"}
class InstallGit(bpy.types.Operator):
"""Installs Git if possible"""
bl_label = "Install Git"
bl_idname = "ifcgit.install_git"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
if IfcGitData.data["git_exe"]:
return False
return True
def execute(self, context):
core.install_git(tool.IfcGit, self)
refresh()
return {"FINISHED"}
class InstallIfcmerge(bpy.types.Operator):
"""Installs ifcmerge if possible"""
bl_label = "Install ifcmerge"
bl_idname = "ifcgit.install_ifcmerge"
bl_options = {"REGISTER"}
def execute(self, context):
core.install_ifcmerge(tool.IfcGit, self)
refresh()
return {"FINISHED"}
+16
View File
@@ -1,6 +1,7 @@
import bpy
import time
import os
import platform
from bonsai.bim.module.ifcgit.data import IfcGitData
@@ -26,7 +27,22 @@ class IFCGIT_PT_panel(bpy.types.Panel):
if not IfcGitData.data["git_exe"]:
row = layout.row()
row.label(text="Git is not installed", icon="ERROR")
if platform.system() == "Windows":
row.operator(
"ifcgit.install_git",
text="Install Git from Microsoft Store",
icon="PACKAGE",
)
return
elif not IfcGitData.data["ifcmerge_exe"]:
# TODO check if ifcmerge is up-to-date
row = layout.row()
row.label(text="ifcmerge is not installed", icon="ERROR")
row.operator(
"ifcgit.install_ifcmerge",
text="Install ifcmerge",
icon="PACKAGE",
)
props = context.scene.IfcGitProperties
+17
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import platform
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
@@ -130,3 +131,19 @@ def entity_log(ifcgit: tool.IfcGit, ifc: tool.Ifc, step_id: int, operator: bpy.t
log_text = ifcgit.entity_log(path_ifc, step_id)
# ERROR is only way to display a multi-line message
operator.report({"ERROR"}, log_text)
def install_git(ifcgit: tool.IfcGit, operator: bpy.types.Operator) -> None:
if platform.system() == "Windows":
ifcgit.install_git_windows(operator=operator)
else:
print("install_git() not implemented")
def install_ifcmerge(ifcgit: tool.IfcGit, operator: bpy.types.Operator) -> None:
if platform.system() == "Windows":
ifcgit.install_ifcmerge_windows(name_exe="ifcmerge.exe", operator=operator)
elif platform.system() == "Linux":
ifcgit.install_ifcmerge_linux(name_exe="ifcmerge", operator=operator)
elif platform.system() == "Darwin":
print("install_ifcmerge() not implemented")
+47
View File
@@ -19,6 +19,8 @@
from __future__ import annotations
import os
import re
import subprocess
import shutil
import bpy
import logging
from bonsai.bim import import_ifc
@@ -546,6 +548,51 @@ class IfcGit:
logtext = "No Git history found :("
return logtext
@classmethod
def install_git_windows(cls, operator: bpy.types.Operator) -> None:
"""Command to install Git on Windows using winget"""
command = ["winget", "install", "--id", "Git.Git", "-e", "--source", "msstore"]
try:
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
operator.report({"ERROR"}, f"Called Process Error occurred: {e}")
except FileNotFoundError:
operator.report({"ERROR"}, "Winget is not available. Make sure Windows Package Manager is installed.")
@classmethod
def install_ifcmerge_linux(cls, name_exe: str, operator: bpy.types.Operator) -> None:
"""Command to install ifcmerge on Linux"""
src_dir = os.path.join(os.path.dirname(__file__), "../libs/desktop")
destdir = os.path.join(os.environ["HOME"], ".local", "bin")
try:
os.makedirs(destdir, exist_ok=True)
shutil.copy(os.path.join(src_dir, name_exe), destdir)
os.chmod(os.path.join(destdir, name_exe), 0o755)
except Exception as e:
operator.report({"ERROR"}, f"Error installing file: {e}")
@classmethod
def install_ifcmerge_windows(cls, name_exe: str, operator: bpy.types.Operator) -> None:
"""Command to install ifcmerge on Windows"""
src_dir = os.path.join(os.path.dirname(__file__), "..\\libs\\desktop")
destdir = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Bonsai", "bin")
try:
os.makedirs(destdir, exist_ok=True)
shutil.copy(os.path.join(src_dir, name_exe), destdir)
except Exception as e:
operator.report({"ERROR"}, f"Error installing file: {e}")
current_path = os.environ["PATH"]
if destdir not in current_path:
os.environ["PATH"] = current_path + ";" + destdir
command = f'setx PATH "%PATH%;{destdir}"'
try:
subprocess.run(command, check=True, shell=True)
print(f"User %PATH% updated permanently with: {destdir}")
except subprocess.CalledProcessError as e:
operator.report({"ERROR"}, f"Error permanently updating %PATH%: {e}")
class IfcGitRepo:
repo: git.Repo = None