diff --git a/src/bonsai/Makefile b/src/bonsai/Makefile index 467477dadc..e3eca3c32d 100644 --- a/src/bonsai/Makefile +++ b/src/bonsai/Makefile @@ -229,6 +229,17 @@ 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/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/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x 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" diff --git a/src/bonsai/bonsai/bim/__init__.py b/src/bonsai/bonsai/bim/__init__.py index 269d0c0ed1..b2f5c9b288 100644 --- a/src/bonsai/bonsai/bim/__init__.py +++ b/src/bonsai/bonsai/bim/__init__.py @@ -259,6 +259,10 @@ 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 diff --git a/src/bonsai/bonsai/bim/module/ifcgit/__init__.py b/src/bonsai/bonsai/bim/module/ifcgit/__init__.py index 27352bbc3e..d48f6e285d 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/__init__.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/__init__.py @@ -37,6 +37,7 @@ classes = ( operator.Push, operator.RefreshGit, operator.SwitchRevision, + operator.InstallGit, prop.IfcGitTag, prop.IfcGitListItem, prop.IfcGitProperties, diff --git a/src/bonsai/bonsai/bim/module/ifcgit/operator.py b/src/bonsai/bonsai/bim/module/ifcgit/operator.py index b059c71b2d..bbb7133122 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/operator.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/operator.py @@ -386,3 +386,25 @@ 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): + """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" + 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"} diff --git a/src/bonsai/bonsai/bim/module/ifcgit/ui.py b/src/bonsai/bonsai/bim/module/ifcgit/ui.py index 860e4325d3..b8d288c77b 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/ui.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/ui.py @@ -1,6 +1,7 @@ import bpy import time import os +import platform from bonsai.bim.module.ifcgit.data import IfcGitData @@ -26,6 +27,12 @@ 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", + icon="PACKAGE", + ) return props = context.scene.IfcGitProperties diff --git a/src/bonsai/bonsai/core/ifcgit.py b/src/bonsai/bonsai/core/ifcgit.py index 17dde89fad..731e3751f2 100644 --- a/src/bonsai/bonsai/core/ifcgit.py +++ b/src/bonsai/bonsai/core/ifcgit.py @@ -17,6 +17,7 @@ # along with Bonsai. If not, see . from __future__ import annotations +import platform from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: @@ -130,3 +131,10 @@ 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") diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 470874a2cb..c55bf08f18 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -430,6 +430,14 @@ 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: + os.environ["PATH"] = current_path + os.pathsep + bin_dir + @classmethod def get_default_selection_keypmap(cls) -> tuple: """keymap to replicate default blender selection behaviour with click and box selection""" diff --git a/src/bonsai/bonsai/tool/ifcgit.py b/src/bonsai/bonsai/tool/ifcgit.py index 0fcc9abc3b..5b0f9b88e5 100644 --- a/src/bonsai/bonsai/tool/ifcgit.py +++ b/src/bonsai/bonsai/tool/ifcgit.py @@ -19,6 +19,7 @@ from __future__ import annotations import os import re +import subprocess import bpy import logging from bonsai.bim import import_ifc @@ -546,6 +547,17 @@ 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", "winget"] + 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.") + class IfcGitRepo: repo: git.Repo = None