Install Git and ifcmerge (revised)

The ZIP installer now contains a `libs/bin` folder that contains
`ifcmerge`. Bonsai on `register()` adds this folder to the system `PATH`
for the current Blender session. Advantage of this is that we don't have
to fiddle with the registry on windows, or install files to
`~/.local/bin` on Linux, it _should_ work on Darwin, updates are
automatic, and we have a mechanism to ship other executables if
required.

(Note that ifcmerge.exe increases the size of the Windows Bonsai ZIP
download by about 7MB)

If Git isn't installed on Windows, the Git panel now offers to install
it from the Windows Package Manager Community Repository using `winget`.
This commit is contained in:
Bruno Postle
2025-01-17 19:22:45 +00:00
parent 5454a7dd14
commit fb9a89f6fa
8 changed files with 27 additions and 73 deletions
+6 -3
View File
@@ -229,12 +229,15 @@ endif
# Required for Desktop icon and file association
cp -r bonsai/libs/desktop build/bonsai/libs/
# folder for executable files
mkdir -p build/bonsai/libs/bin
# 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
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2024-06-24/ifcmerge.zip
cd build/bonsai/libs/bin && unzip ifcmerge.zip && rm ifcmerge.zip
else
cd build/bonsai/libs/desktop && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge
endif
# Generate translations module for Bonsai build
+3
View File
@@ -258,6 +258,9 @@ def register():
icons = icon_preview
bpy.app.translations.register("bonsai", translations_dict)
import bonsai.tool as tool
tool.Blender.ensure_bin_in_path()
def unregister():
global icons
@@ -38,7 +38,6 @@ classes = (
operator.RefreshGit,
operator.SwitchRevision,
operator.InstallGit,
operator.InstallIfcmerge,
prop.IfcGitTag,
prop.IfcGitListItem,
prop.IfcGitProperties,
@@ -389,7 +389,9 @@ class ObjectLog(bpy.types.Operator):
class InstallGit(bpy.types.Operator):
"""Installs Git if possible"""
"""Install Git Version Control System from the
Windows Package Manager Community Repository,
requires restarting Blender after installation"""
bl_label = "Install Git"
bl_idname = "ifcgit.install_git"
@@ -406,16 +408,3 @@ class InstallGit(bpy.types.Operator):
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"}
+1 -10
View File
@@ -30,19 +30,10 @@ class IFCGIT_PT_panel(bpy.types.Panel):
if platform.system() == "Windows":
row.operator(
"ifcgit.install_git",
text="Install Git from Microsoft Store",
text="Install Git",
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
-9
View File
@@ -138,12 +138,3 @@ def install_git(ifcgit: tool.IfcGit, operator: bpy.types.Operator) -> None:
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")
+13
View File
@@ -21,6 +21,7 @@ import bpy
import bmesh
import json
import os
import platform
from ifcopenshell import entity_instance
import ifcopenshell.api
import ifcopenshell.util.element
@@ -430,6 +431,18 @@ class Blender(bonsai.core.tool.Blender):
return blender_path
return bpy.path.abspath("//") / blender_path
@classmethod
def ensure_bin_in_path(cls) -> None:
"""Check 'bin' folder is in PATH, if not add for this session"""
bin_dir = str(Path(__file__).parent.parent.resolve() / "libs" / "bin")
current_path = os.environ["PATH"]
if bin_dir not in current_path:
if platform.system() == "Windows":
path_separator = ";"
else:
path_separator = ":"
os.environ["PATH"] = current_path + path_separator + bin_dir
@classmethod
def get_default_selection_keypmap(cls) -> tuple:
"""keymap to replicate default blender selection behaviour with click and box selection"""
+1 -36
View File
@@ -20,7 +20,6 @@ from __future__ import annotations
import os
import re
import subprocess
import shutil
import bpy
import logging
from bonsai.bim import import_ifc
@@ -551,7 +550,7 @@ class IfcGit:
@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"]
command = ["winget", "install", "--id", "Git.Git", "-e", "--source", "winget"]
try:
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
@@ -559,40 +558,6 @@ class IfcGit:
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