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 # Required for Desktop icon and file association
cp -r bonsai/libs/desktop build/bonsai/libs/ cp -r bonsai/libs/desktop build/bonsai/libs/
# folder for executable files
mkdir -p build/bonsai/libs/bin
# required for three-way git merging # required for three-way git merging
ifeq ($(PLATFORM), win) 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/bin && 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 && unzip ifcmerge.zip && rm ifcmerge.zip
else 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 endif
# Generate translations module for Bonsai build # Generate translations module for Bonsai build
+3
View File
@@ -258,6 +258,9 @@ def register():
icons = icon_preview icons = icon_preview
bpy.app.translations.register("bonsai", translations_dict) bpy.app.translations.register("bonsai", translations_dict)
import bonsai.tool as tool
tool.Blender.ensure_bin_in_path()
def unregister(): def unregister():
global icons global icons
@@ -38,7 +38,6 @@ classes = (
operator.RefreshGit, operator.RefreshGit,
operator.SwitchRevision, operator.SwitchRevision,
operator.InstallGit, operator.InstallGit,
operator.InstallIfcmerge,
prop.IfcGitTag, prop.IfcGitTag,
prop.IfcGitListItem, prop.IfcGitListItem,
prop.IfcGitProperties, prop.IfcGitProperties,
@@ -389,7 +389,9 @@ class ObjectLog(bpy.types.Operator):
class InstallGit(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_label = "Install Git"
bl_idname = "ifcgit.install_git" bl_idname = "ifcgit.install_git"
@@ -406,16 +408,3 @@ class InstallGit(bpy.types.Operator):
core.install_git(tool.IfcGit, self) core.install_git(tool.IfcGit, self)
refresh() refresh()
return {"FINISHED"} 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": if platform.system() == "Windows":
row.operator( row.operator(
"ifcgit.install_git", "ifcgit.install_git",
text="Install Git from Microsoft Store", text="Install Git",
icon="PACKAGE", icon="PACKAGE",
) )
return 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 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) ifcgit.install_git_windows(operator=operator)
else: else:
print("install_git() not implemented") 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 bmesh
import json import json
import os import os
import platform
from ifcopenshell import entity_instance from ifcopenshell import entity_instance
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -430,6 +431,18 @@ class Blender(bonsai.core.tool.Blender):
return blender_path return blender_path
return bpy.path.abspath("//") / 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 @classmethod
def get_default_selection_keypmap(cls) -> tuple: def get_default_selection_keypmap(cls) -> tuple:
"""keymap to replicate default blender selection behaviour with click and box selection""" """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 os
import re import re
import subprocess import subprocess
import shutil
import bpy import bpy
import logging import logging
from bonsai.bim import import_ifc from bonsai.bim import import_ifc
@@ -551,7 +550,7 @@ class IfcGit:
@classmethod @classmethod
def install_git_windows(cls, operator: bpy.types.Operator) -> None: def install_git_windows(cls, operator: bpy.types.Operator) -> None:
"""Command to install Git on Windows using winget""" """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: try:
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@@ -559,40 +558,6 @@ class IfcGit:
except FileNotFoundError: except FileNotFoundError:
operator.report({"ERROR"}, "Winget is not available. Make sure Windows Package Manager is installed.") 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: class IfcGitRepo:
repo: git.Repo = None repo: git.Repo = None