From cee995a80c5e33727a685a1e4d721451e1a6983d Mon Sep 17 00:00:00 2001 From: Felipe Franco Date: Fri, 3 Apr 2026 22:55:40 -0700 Subject: [PATCH 1/2] fix blender-to-zbrush update by launching GoZBrushFromApp and syncing GoZ paths --- gob_export.py | 54 +++++++++++++++++--------------- gob_import.py | 16 +++++++--- paths.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++--- preferences.py | 21 +++++-------- 4 files changed, 127 insertions(+), 47 deletions(-) diff --git a/gob_export.py b/gob_export.py index 7d20875..40680ad 100644 --- a/gob_export.py +++ b/gob_export.py @@ -531,32 +531,33 @@ def exportGoZ(self, scn, obj, path_export): def execute(self, context): - if utils.prefs().custom_pixologoc_path: - paths.PATH_GOZ = utils.prefs().pixologoc_path + paths.set_goz_root( + utils.prefs().pixologoc_path if utils.prefs().custom_pixologoc_path else None + ) PATH_PROJECT = utils.prefs().project_path #setup GoZ configuration #if not os.path.isfile(f"{paths.PATH_GOZ}/GoZApps/Blender/GoZ_Info.txt"): + source_GoZ_Info_dir = os.path.join(paths.PATH_GOB, "Blender") + target_GoZ_Info_dir = os.path.join(paths.PATH_GOZ, "GoZApps", "Blender") + source_GoZ_Info = os.path.join(source_GoZ_Info_dir, "GoZ_Info.txt") + target_GoZ_Info = os.path.join(target_GoZ_Info_dir, "GoZ_Info.txt") try: #install in GoZApps if missing - source_GoZ_Info = os.path.join(paths.PATH_GOB, "Blender") - target_GoZ_Info = os.path.join(paths.PATH_GOZ, "GoZApps", "Blender") - print(source_GoZ_Info, target_GoZ_Info) - shutil.copytree(source_GoZ_Info, target_GoZ_Info, symlinks=True) - except FileExistsError: #if blender folder is found update the info file - source_GoZ_Info = os.path.join(paths.PATH_GOB, "Blender", "GoZ_Info.txt") - target_GoZ_Info = os.path.join(paths.PATH_GOZ, "GoZApps", "Blender", "GoZ_Info.txt") - shutil.copy2(source_GoZ_Info, target_GoZ_Info) + print(source_GoZ_Info_dir, target_GoZ_Info_dir) + shutil.copytree(source_GoZ_Info_dir, target_GoZ_Info_dir, symlinks=True) + except FileExistsError: + pass + except Exception as e: + print(e) - #write blender path to GoZ configuration - #if not os.path.isfile(f"{paths.PATH_GOZ}/GoZApps/Blender/GoZ_Config.txt"): + try: + shutil.copy2(source_GoZ_Info, target_GoZ_Info) with open(os.path.join(paths.PATH_GOZ, "GoZApps", "Blender", "GoZ_Config.txt"), 'wt') as GoB_Config: blender_path = os.path.join(paths.PATH_BLENDER).replace('\\', '/') GoB_Config.write(f'PATH = "{blender_path}"') - #specify GoZ application with open(os.path.join(paths.PATH_GOZ, "GoZBrush", "GoZ_Application.txt"), 'wt') as GoZ_Application: - GoZ_Application.write("Blender") - + GoZ_Application.write("Blender") except Exception as e: print(e) @@ -701,18 +702,21 @@ def execute(self, context): print(e) # only run if PATH_OBJLIST file file is not empty, else zbrush errors - if not paths.is_file_empty(paths.PATH_OBJLIST): - path_exists = paths.find_zbrush(self, context, paths.isMacOS) - if utils.prefs().export_run_zbrush: + if not paths.is_file_empty(paths.PATH_OBJLIST) and utils.prefs().export_run_zbrush: + goz_launcher = paths.get_gozbrush_launcher() + if goz_launcher: + print("GoB GoZ launcher:", goz_launcher) + Popen([goz_launcher]) + else: + path_exists = paths.find_zbrush(self, context, paths.isMacOS) if not path_exists: bpy.ops.gob.search_zbrush('INVOKE_DEFAULT') - else: - if paths.isMacOS: - print("OSX Popen: ", utils.prefs().zbrush_exec) - Popen(['open', '-a', utils.prefs().zbrush_exec, paths.PATH_SCRIPT]) - else: #windows - print("Windows Popen: ", utils.prefs().zbrush_exec) - Popen([utils.prefs().zbrush_exec, paths.PATH_SCRIPT], shell=True) + elif paths.isMacOS: + print("OSX Popen: ", utils.prefs().zbrush_exec) + Popen(['open', '-a', utils.prefs().zbrush_exec, paths.PATH_SCRIPT]) + else: #windows + print("Windows Popen: ", utils.prefs().zbrush_exec) + Popen([utils.prefs().zbrush_exec, paths.PATH_SCRIPT]) # restore object context if context.object and currentContext: diff --git a/gob_import.py b/gob_import.py index 8b51298..4a3f9ee 100644 --- a/gob_import.py +++ b/gob_import.py @@ -595,13 +595,14 @@ def GoZit(self, pathFile): def execute(self, context): - if utils.prefs().custom_pixologoc_path: - paths.PATH_GOZ = utils.prefs().pixologoc_path + paths.set_goz_root( + utils.prefs().pixologoc_path if utils.prefs().custom_pixologoc_path else None + ) global gob_import_cache goz_obj_paths = [] try: - with open(os.path.join(paths.PATH_GOZ, "GoZBrush", "GoZ_ObjectList.txt"), 'rt') as goz_objs_list: + with open(paths.PATH_OBJLIST, 'rt') as goz_objs_list: goz_obj_paths.extend(f'{line.strip()}.GoZ' for line in goz_objs_list) except PermissionError: if utils.prefs().debug_output: @@ -653,6 +654,10 @@ def execute(self, context): def invoke(self, context, event): + paths.set_goz_root( + utils.prefs().pixologoc_path if utils.prefs().custom_pixologoc_path else None + ) + if utils.prefs().debug_output: print("ACTION: ", self.action) @@ -693,9 +698,12 @@ def invoke(self, context, event): def run_import_periodically(): # print("Runing timers update check") global cached_last_edition_time, run_background_update + paths.set_goz_root( + utils.prefs().pixologoc_path if utils.prefs().custom_pixologoc_path else None + ) try: - file_edition_time = os.path.getmtime(os.path.join(paths.PATH_GOZ, "GoZBrush", "GoZ_ObjectList.txt")) + file_edition_time = os.path.getmtime(paths.PATH_OBJLIST) #print("file_edition_time: ", file_edition_time, end='\n\n') except Exception as e: print(e) diff --git a/paths.py b/paths.py index e39a24f..c388832 100644 --- a/paths.py +++ b/paths.py @@ -24,13 +24,88 @@ from . import ui, utils, gob_import +def goz_root_candidates(): + system = platform.system() + if system == 'Windows': + public_root = os.environ.get('PUBLIC', os.path.join("C:\\", "Users", "Public")) + return [ + os.path.join(public_root, "Documents", "Maxon"), + os.path.join(public_root, "Documents", "Pixologic"), + os.path.join(public_root, "Maxon"), + os.path.join(public_root, "Pixologic"), + ] + if system == 'Darwin': + return [ + os.path.join(os.sep, "Users", "Shared", "Maxon"), + os.path.join(os.sep, "Users", "Shared", "Pixologic"), + ] + return [] + + +def is_goz_root(path): + if not path: + return False + return any( + os.path.isdir(os.path.join(path, folder)) + for folder in ("GoZApps", "GoZBrush", "GoZProjects") + ) + + +def resolve_goz_root(path_override=None): + if path_override: + return os.path.normpath(path_override) + + for candidate in goz_root_candidates(): + if is_goz_root(candidate): + return os.path.normpath(candidate) + + candidates = goz_root_candidates() + return os.path.normpath(candidates[0]) if candidates else False + + +def set_goz_root(path_override=None): + global PATH_GOZ, PATH_OBJLIST, PATH_CONFIG, PATH_VARS + + PATH_GOZ = resolve_goz_root(path_override) + if PATH_GOZ: + PATH_OBJLIST = os.path.join(PATH_GOZ, "GoZBrush", "GoZ_ObjectList.txt") + PATH_CONFIG = os.path.join(PATH_GOZ, "GoZBrush", "GoZ_Config.txt") + PATH_VARS = os.path.join(PATH_GOZ, "GoZProjects", "Default", "GoB_variables.zvr") + else: + PATH_OBJLIST = False + PATH_CONFIG = False + PATH_VARS = False + + return PATH_GOZ + + +def get_gozbrush_launcher(): + if not PATH_GOZ: + return None + + if platform.system() == 'Windows': + candidates = [os.path.join(PATH_GOZ, "GoZBrush", "GoZBrushFromApp.exe")] + elif platform.system() == 'Darwin': + candidates = [ + os.path.join(PATH_GOZ, "GoZBrush", "GoZBrushFromApp.app", "Contents", "MacOS", "GoZBrushFromApp"), + os.path.join(PATH_GOZ, "GoZBrush", "GoZBrushFromApp"), + ] + else: + candidates = [] + + for candidate in candidates: + if os.path.isfile(candidate): + return candidate + return None + + def gob_init_os_paths(): isMacOS = False useZSH = False if platform.system() == 'Windows': print("GoB Found System: ", platform.system()) isMacOS = False - PATH_GOZ = os.path.join(os.environ['PUBLIC'] , "Pixologic") + PATH_GOZ = resolve_goz_root() elif platform.system() == 'Darwin': #osx print("GoB Found System: ", platform.system()) @@ -45,7 +120,7 @@ def gob_init_os_paths(): isMacOS = True #print(os.path.isfile("/Users/Shared/Pixologic/GoZBrush/GoZBrushFromApp.app/Contents/MacOS/GoZBrushFromApp")) - PATH_GOZ = os.path.join("Users", "Shared", "Pixologic") + PATH_GOZ = resolve_goz_root() else: print("GoB Unkonwn System: ", platform.system()) PATH_GOZ = False ## NOTE: GOZ seems to be missing, reinstall from zbrush @@ -160,7 +235,7 @@ def execute(self, context): Popen(['open', '-a', GOZ_INSTALLER]) else: GOZ_INSTALLER = os.path.join(path, "Troubleshoot Help", "GoZ_for_ZBrush_Installer_WIN.exe") - Popen([GOZ_INSTALLER], shell=True) + Popen([GOZ_INSTALLER]) else: bpy.ops.gob.search_zbrush('INVOKE_DEFAULT') - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} diff --git a/preferences.py b/preferences.py index ec33f89..0c5fd84 100644 --- a/preferences.py +++ b/preferences.py @@ -45,7 +45,7 @@ class GoB_Preferences(AddonPreferences): #GLOBAL zbrush_exec: StringProperty( name="ZBrush Path", - description="Select Zbrush executable (C:\Program Files\Pixologic\ZBrush\ZBrush.exe). " + description="Select ZBrush executable (for example C:\\Program Files\\Maxon ZBrush 2025\\ZBrush.exe). " "\nIf not specified the system default for Zscript (.zsc) files will be used", subtype='FILE_PATH', default="") # Default: "" @@ -58,23 +58,16 @@ class GoB_Preferences(AddonPreferences): default=False) # Default: False custom_pixologoc_path: BoolProperty( - name="Custom Pixologic Public Path", - description="This will allow you to set a custom Public Pixologic Path, this is where ZBrush stores GoZ configurations", + name="Custom GoZ Public Path", + description="Set a custom public GoZ path when ZBrush stores GoZ data outside the detected Maxon or Pixologic folder", default=False) # Default: False - if platform.system() == 'Windows': - PATH_GOZ = os.path.join(os.environ['PUBLIC'] , "Pixologic") - elif platform.system() == 'Darwin': #osx - PATH_GOZ = os.path.join("Users", "Shared", "Pixologic") - else: - PATH_GOZ = False - pixologoc_path: StringProperty( - name="Pixologic Public Path", - description="Set public pixologic path, this needs to be a valid folder which zbrush accesses." - "By default this folder is on the windows system drive under C:\\Users\\Public\\Pixologic", + name="GoZ Public Path", + description="Set the public GoZ folder used by ZBrush." + "\nExamples: C:\\Users\\Public\\Documents\\Maxon or C:\\Users\\Public\\Documents\\Pixologic", subtype='DIR_PATH', - default=PATH_GOZ) # Default: PATH_GOZ + default=paths.PATH_GOZ or "") # Default: PATH_GOZ project_path: StringProperty( name="Project Path", From f36be310646dfc289fc27cf37113779c4a259932 Mon Sep 17 00:00:00 2001 From: "Felipe P. Franco" Date: Fri, 3 Apr 2026 23:12:10 -0700 Subject: [PATCH 2/2] Revise GoB installation instructions in README Updated installation instructions for GoB addon and removed outdated update section. --- README.md | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index dbdd1dc..d363721 100644 --- a/README.md +++ b/README.md @@ -15,35 +15,18 @@ You can transfer: * Displacement map ## Clean Installation of GoB -1. Remove your old GoB addon from your Blender Addons Folder -2. Copy the extracted GoB addon into your Blender Addon Folder +1. Remove your old GoB addon from your Blender Addons Folder if you have one sitting there. +2. Install the zip via Preferences -> Add-ons -> Install from Disk (arrow top right corner). 3. Start Blender and enable the GoB addon in the Preferences > Addons menu and safe your preferences 4. Select a object you want to send to Zbrush and press the Export Button in the Header. This will configure Zbrush to know that it is communicating with Blender, Run Zbrush and load in your Object. 5. Restart Zbrush and that is it. -## Update GoB -1. Remove your old GoB addon from your Blender Addons Folder -2. Copy the extracted GoB addon into your Blender Addon Folder - - -### Configure Blender -**_Note**: If you have a previous version, remove it via the Addon panel (unroll the GoB entry and remove it) before continuing._ - -**_Note**: Github breaks (changes) name of zip file and the first (root) folder inside zip, when you download addon. Both zip file and first folder inside should be named: 'GoB' -The addon final location sould look like this: -* C:\Users\XXXXX\AppData\Roaming\Blender Foundation\Blender\2.80\scripts\addons\**GoB** - -1. In Blender, open the addon panel, then click the _'Install From Files...'_ button at the bottom. Select the `GoB.zip` file, this will install the addon inside the correct folder. -3. Check the **GoB** box and save the User preferences to launch it at startup. Then click on the Import icon on the header (on top of Blender) to activate autoloading. - - ## Usage The addon adds two icons Import/Export to the top info panel: * By clicking on the Export icon, you export the selected mesh objects into ZBrush. * By clicking on the Import icon, you enable autoloading mode. Latest objects are imported from GoZ. - # Acknowledgements This script was originally written by user "Stunton" and posted [here on ZBrushCentral](http://www.zbrushcentral.com/showthread.php?127419-GoB-an-unofficial-GoZ-for-Blender).