From c4ffe90573ee6f8f4711bb44c65ec2cb57192d8f Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 18 Dec 2023 16:56:15 -0500 Subject: [PATCH] Interim Submit --- .editorconfig | 25 + .gitignore | 2 +- .pylintrc | 29 + .vscode/settings.json | 21 + __init__.py | 16 +- addon_updater.py | 50 +- constants.py | 115 +-- generic_utils.py | 198 ----- img_utils.py | 12 - marmoset_ops.py | 212 ------ node_group_utils.py | 523 -------------- operators/__init__.py | 0 operators/marmoset.py | 236 ++++++ mat_id_ops.py => operators/material.py | 61 +- operators.py => operators/operators.py | 506 +++++++------ preferences.py | 613 +++++++++------- render_setup_utils.py | 93 --- ui.py | 677 ++++++++++-------- utils/__init__.py | 0 .../baker.py | 256 ++++--- utils/generic.py | 230 ++++++ marmoset_utils.py => utils/marmoset.py | 35 +- utils/node.py | 655 +++++++++++++++++ utils/render.py | 145 ++++ scene_setup_utils.py => utils/scene.py | 204 +++--- 25 files changed, 2779 insertions(+), 2135 deletions(-) create mode 100644 .editorconfig create mode 100644 .pylintrc create mode 100644 .vscode/settings.json delete mode 100644 generic_utils.py delete mode 100644 img_utils.py delete mode 100644 marmoset_ops.py delete mode 100644 node_group_utils.py create mode 100644 operators/__init__.py create mode 100644 operators/marmoset.py rename mat_id_ops.py => operators/material.py (67%) rename operators.py => operators/operators.py (56%) delete mode 100644 render_setup_utils.py create mode 100644 utils/__init__.py rename baker_setup_cleanup_utils.py => utils/baker.py (65%) create mode 100644 utils/generic.py rename marmoset_utils.py => utils/marmoset.py (83%) create mode 100644 utils/node.py create mode 100644 utils/render.py rename scene_setup_utils.py => utils/scene.py (57%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..54da87e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Python +[*.py] +indent_style = space +indent_size = 4 +max_line_length = 100 + +# YAML +[*.yaml] +indent_style = space +indent_size = 2 + +# JSON +[*.json] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index ac4fa63..3a124a1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,6 @@ LICENSE README.md -Temp/ +_temp/ grabdocgit_updater/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..d761133 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,29 @@ +[MASTER] + +ignore=__pycache__, .vscode, _source +disable= + no-member, + too-many-arguments, + too-few-public-methods, + logging-format-interpolation, + missing-module-docstring, + missing-function-docstring, + missing-class-docstring, + no-name-in-module, + c-extension-no-member, + fixme, + invalid-name, + consider-using-with, + wrong-import-position, + consider-using-f-string, + wrong-import-order, + ungrouped-imports, + unnecessary-lambda, + unused-wildcard-import, + attribute-defined-outside-init, + assignment-from-no-return + + +[FORMAT] + +max-line-length=88 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fd07c70 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "python.analysis.typeCheckingMode": "off", + "python.analysis.autoImportCompletions": true, + "cSpell.words": [ + "backface", + "BLACKMAN", + "BSDF", + "colorspace", + "depsgraph", + "eevee", + "frustrum", + "gtao", + "matcap", + "mathutils", + "Metalness", + "overscan", + "ssao", + "Toolbag", + "viewlayer" +] +} diff --git a/__init__.py b/__init__.py index 13aa4b2..d9c8eb7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,9 +1,9 @@ bl_info = { "name": "GrabDoc Pro", "author": "Ethan Simon-Law", - "location": "3D View > Sidebar > GrabDoc Tab", + "location": "3D View > Sidebar > GrabDoc", "version": (1, 4, 0), - "blender": (3, 5, 0), + "blender": (4, 0, 1), "tracker_url": "https://discord.com/invite/wHAyVZG", "category": "3D View" } @@ -13,11 +13,11 @@ module_names = ( - "ui", - "operators", - "mat_id_ops", - "marmoset_ops", - "preferences" + "operators.operators", + "operators.material", + "operators.marmoset", + "preferences", + "ui" ) modules = [] @@ -26,7 +26,7 @@ if module_name in locals(): modules.append(importlib.reload(locals()[module_name])) else: - modules.append(importlib.import_module("." + module_name, __package__)) + modules.append(importlib.import_module(f".{module_name}", __package__)) def register(): for mod in modules: diff --git a/addon_updater.py b/addon_updater.py index 027810a..ff4e9e7 100644 --- a/addon_updater.py +++ b/addon_updater.py @@ -176,7 +176,7 @@ def backup_current(self): return self._backup_current @backup_current.setter def backup_current(self, value): - if value == None: + if value is None: self._backup_current = False return else: @@ -187,7 +187,7 @@ def backup_ignore_patterns(self): return self._backup_ignore_patterns @backup_ignore_patterns.setter def backup_ignore_patterns(self, value): - if value == None: + if value is None: self._backup_ignore_patterns = None return elif type(value) != type(['list']): @@ -271,7 +271,7 @@ def include_branch_list(self): @include_branch_list.setter def include_branch_list(self, value): try: - if value == None: + if value is None: self._include_branch_list = ['master'] elif type(value) != type(['master']) or value==[]: raise ValueError("include_branch_list should be a list of valid branches") @@ -298,7 +298,7 @@ def json(self): @property def latest_release(self): - if self._latest_release == None: + if self._latest_release is None: return None return self._latest_release @@ -317,7 +317,7 @@ def overwrite_patterns(self): return self._overwrite_patterns @overwrite_patterns.setter def overwrite_patterns(self, value): - if value == None: + if value is None: self._overwrite_patterns = ["*.py","*.pyc"] elif type(value) != type(['']): raise ValueError("overwrite_patterns needs to be in a list format") @@ -339,7 +339,7 @@ def remove_pre_update_patterns(self): return self._remove_pre_update_patterns @remove_pre_update_patterns.setter def remove_pre_update_patterns(self, value): - if value == None: + if value is None: self._remove_pre_update_patterns = [] elif type(value) != type(['']): raise ValueError("remove_pre_update_patterns needs to be in a list format") @@ -372,7 +372,7 @@ def stage_path(self): return self._updater_path @stage_path.setter def stage_path(self, value): - if value == None: + if value is None: if self._verbose: print("Aborting assigning stage_path, it's null") return elif value != None and not os.path.exists(value): @@ -401,7 +401,7 @@ def tags(self): @property def tag_latest(self): - if self._tag_latest == None: + if self._tag_latest is None: return None return self._tag_latest["name"] @@ -454,7 +454,7 @@ def version_max_update(self): return self._version_max_update @version_max_update.setter def version_max_update(self, value): - if value == None: + if value is None: self._version_max_update = None return if type(value) != type((1,2,3)): @@ -469,7 +469,7 @@ def version_min_update(self): return self._version_min_update @version_min_update.setter def version_min_update(self, value): - if value == None: + if value is None: self._version_min_update = None return if type(value) != type((1,2,3)): @@ -588,14 +588,14 @@ def get_tags(self): } self._tags = [include] + self._tags # append to front - if self._tags == None: + if self._tags is None: # some error occurred self._tag_latest = None self._tags = [] return elif self._prefiltered_tag_count == 0 and self._include_branches == False: self._tag_latest = None - if self._error == None: # if not None, could have had no internet + if self._error is None: # if not None, could have had no internet self._error = "No releases found" self._error_msg = "No releases or tags found on this repository" if self._verbose: print("No releases or tags found on this repository") @@ -905,7 +905,7 @@ def unpack_staged_zip(self,clean=False): if os.path.isfile(os.path.join(unpath,"__init__.py")) == False: dirlist = os.listdir(unpath) if len(dirlist)>0: - if self._subfolder_path == "" or self._subfolder_path == None: + if self._subfolder_path == "" or self._subfolder_path is None: unpath = os.path.join(unpath, dirlist[0]) else: unpath = os.path.join(unpath, self._subfolder_path) @@ -1083,7 +1083,7 @@ def urlretrieve(self, urlfile, filepath): def version_tuple_from_text(self,text): - if text == None: return () + if text is None: return () # should go through string and remove all non-integers, # and for any given break split into a different section @@ -1125,7 +1125,7 @@ def check_for_update_async(self, callback=None): elif self._async_checking == True: if self._verbose: print("Skipping async check, already started") return # already running the bg thread - elif self._update_ready == None: + elif self._update_ready is None: self.start_async_check_update(False, callback) @@ -1139,7 +1139,7 @@ def check_for_update_now(self, callback=None): if self._async_checking == True: if self._verbose: print("Skipping async check, already started") return # already running the bg thread - elif self._update_ready == None: + elif self._update_ready is None: self.start_async_check_update(True, callback) else: self._update_ready = None @@ -1160,11 +1160,11 @@ def check_for_update(self, now=False): if self._update_ready != None and now == False: return (self._update_ready,self._update_version,self._update_link) - if self._current_version == None: + if self._current_version is None: raise ValueError("current_version not yet defined") - if self._repo == None: + if self._repo is None: raise ValueError("repo not yet defined") - if self._user == None: + if self._user is None: raise ValueError("username not yet defined") self.set_updater_json() # self._json @@ -1325,7 +1325,7 @@ def run_update(self,force=False,revert_tag=None,clean=False,callback=None): self._addon_package, "Update stopped, new version not ready") return "Update stopped, new version not ready" - elif self._update_link == None: + elif self._update_link is None: # this shouldn't happen if update is ready if self._verbose: print("Update stopped, update link unavailable") @@ -1353,7 +1353,7 @@ def run_update(self,force=False,revert_tag=None,clean=False,callback=None): return res else: - if self._update_link == None: + if self._update_link is None: if self._verbose: print("Update stopped, could not get link") return "Update stopped, could not get link" @@ -1427,7 +1427,7 @@ def get_json_path(self): def set_updater_json(self): """Load or initialize JSON dictionary data for updater state""" - if self._updater_path == None: + if self._updater_path is None: raise ValueError("updater_path is not defined") elif os.path.isdir(self._updater_path) == False: os.makedirs(self._updater_path) @@ -1586,7 +1586,7 @@ def get_zip_url(self, name, updater): name=name) def parse_tags(self, response, updater): - if response == None: + if response is None: return [] return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["name"], updater)} for tag in response["values"]] @@ -1617,7 +1617,7 @@ def form_branch_url(self, branch, updater): "/zipball/",branch) def parse_tags(self, response, updater): - if response == None: + if response is None: return [] return response @@ -1660,7 +1660,7 @@ def get_zip_url(self, sha, updater): # return self.form_repo_url(updater)+"/repository/archive.zip?sha:"+id def parse_tags(self, response, updater): - if response == None: + if response is None: return [] return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["commit"]["id"], updater)} for tag in response] diff --git a/constants.py b/constants.py index b439722..bc1c32e 100644 --- a/constants.py +++ b/constants.py @@ -1,38 +1,54 @@ -# NOTE This is a basic grouping of constants for specific object +# NOTE: This is a basic grouping of constants for specific object # names that are frequently reused. An improved approach would # be to make a system that doesn't rely on naming conventions and -# insteads reads GD-specific attributes, or whatever alternative relies +# instead reads GD-specific attributes, or whatever alternative relies # on just using less constants. This is more a temp cope for old code. -# from .constants import GlobalVariableConstants as GlobalVarConst +# from .constants import GlobalVariableConstants as Global + + +NAME = "GrabDoc Pro" +VERSION = (1, 4, 0) +BLENDER_VERSION = (4, 0, 1) class GlobalVariableConstants: """A collection of constants used for global variable standardization""" - ID_PREFIX = "grab_doc" + ID_PREFIX = "grab_doc" # TODO: wrong prefix? - REFERENCE_NAME = "GD_Reference" - TRIM_CAMERA_NAME = "GD_Trim Camera" - BG_PLANE_NAME = "GD_Background Plane" - COLL_NAME = "GrabDoc (do not touch contents)" - COLL_OB_NAME = "GrabDoc Objects (put objects here)" + REFERENCE_NAME = "GD_Reference" + TRIM_CAMERA_NAME = "GD_Trim Camera" + BG_PLANE_NAME = "GD_Background Plane" + COLL_NAME = "GrabDoc (do not touch contents)" + COLL_OB_NAME = "GrabDoc Objects (put objects here)" HEIGHT_GUIDE_NAME = "GD_Height Guide" ORIENT_GUIDE_NAME = "GD_Orient Guide" - GD_MATERIAL_NAME = "GD_Material (do not touch contents)" - GD_PREFIX = "GD_" - GD_LOW_PREFIX = "GD_low" + GD_MATERIAL_NAME = "GD_Material (do not touch contents)" + + GD_PREFIX = "GD_" + GD_LOW_PREFIX = "GD_low" GD_HIGH_PREFIX = "GD_high" - MAT_ID_PREFIX = "GD_ID" + + MAT_ID_PREFIX = "GD_ID" MAT_ID_RAND_PREFIX = "GD_RANDOM_ID" - NG_NORMAL_NAME = "GD_Normal" - NG_AO_NAME = "GD_Ambient Occlusion" - NG_HEIGHT_NAME = "GD_Height" - NG_ALPHA_NAME = "GD_Alpha" - NG_ALBEDO_NAME = "GD_Albedo" + + NG_NORMAL_NAME = "GD_Normal" + NG_AO_NAME = "GD_Ambient Occlusion" + NG_HEIGHT_NAME = "GD_Height" + NG_ALPHA_NAME = "GD_Alpha" + NG_ALBEDO_NAME = "GD_Albedo" NG_ROUGHNESS_NAME = "GD_Roughness" NG_METALNESS_NAME = "GD_Metalness" - UNRENDERABLE_TYPES = ('EMPTY', 'VOLUME', 'ARMATURE', 'LATTICE', 'LIGHT', 'LIGHT_PROBE', 'CAMERA') + INVALID_RENDER_TYPES = ( + 'EMPTY', + 'VOLUME', + 'ARMATURE', + 'LATTICE', + 'LIGHT', + 'LIGHT_PROBE', + 'CAMERA' + ) FORMAT_MATCHED_EXTENSIONS = { 'TIFF': 'tif', @@ -41,37 +57,34 @@ class GlobalVariableConstants: 'PNG': 'png' } - NG_NODE_WARNING = """ -This is a passthrough node from GrabDoc, once you Exit Map Preview every node link will be returned + NG_NODE_WARNING = \ +"""This is a passthrough node from GrabDoc, once you Exit Map Preview every node link will be returned to original positions. It's best not to touch the contents of the node group (or material) but if you do anyways it shouldn't be overwritten by GrabDoc until the node group is removed from file, which -only happens when you use the `Remove Setup` operator :) -""" +only happens when you use the `Remove Setup` operator.""" - PACK_MAPS_WARNING = """ -Map Packing is a new feature in GrabDoc for optimizing textures being exported (usually -directly to engine) by cramming greyscale baked maps into each RGBA channel of a single -texture reducing the amount of texture samples used, and by extension the memory footprint. + PACK_MAPS_WARNING = \ +"""Map Packing is a new feature in GrabDoc for optimizing textures being exported (usually +directly to engine) by cramming grayscale baked maps into each RGBA channel of a single +texture reducing the amount of texture samples used and by extension the memory footprint. This is meant to be a simple alternative to pit-stopping over to compositing software to finish the job, but its usability is limited. Map Packing in GrabDoc is new, so here's a few things to keep note of: -\u2022 Only greyscale maps can currently be packed +\u2022 Only grayscale maps can currently be packed \u2022 Map packing isn't supported for Marmoset bakes \u2022 Any successfully packed maps will not also be exported as their own maps and will also ignore `Import as Material` option \u2022 The default selected packed channels don't represent the default enabled -bake maps, meaning without intervention G, B, and A channels will be empty. -""" +bake maps, meaning without intervention G, B, and A channels will be empty.""" - PREVIEW_WARNING = """ -Live Material Preview is a feature that allows you to pre-visualize your bake maps in real-time! + PREVIEW_WARNING = \ +"""Live Material Preview allows you to visualize your bake maps in real-time! -CONSIDER THIS WARNING: This feature is intended for previewing your materials, NOT for working -while inside a preview. Once finished, please exit previews to avoid potential scene altering changes. +This feature is intended for previewing your materials, NOT for working while inside +the preview. Once finished please exit previews to avoid scene altering changes. -Pressing `OK` will dismiss this warning permanently for this current project file. -""" +Pressing `OK` will dismiss this warning permanently for the current project file.""" class ErrorCodeConstants: @@ -79,17 +92,25 @@ class ErrorCodeConstants: NO_OBJECTS_SELECTED = "There are no objects selected" - TRIM_CAM_NOT_FOUND = "Trim Camera not found, refresh the scene to set everything up properly" - NO_OBJECTS_BAKE_GROUPS = "You have 'Use Bake Group' turned on, but no objects are inside the corresponding collection" - NO_VALID_PATH_SET = "There is no export path set" - - MAT_SLOTS_WITHOUT_LINKS = "Material slots found without links & will be rendered using the sockets default value" - - MARMO_EXPORT_COMPLETE = "Export completed! Opening Marmoset Toolbag..." - MARMO_RE_EXPORT_COMPLETE = "Models re-exported! Check Marmoset Toolbag" - - OFFLINE_RENDER_COMPLETE = "Offline render completed!" - EXPORT_COMPLETE = "Export completed!" + TRIM_CAM_NOT_FOUND = \ + "Trim Camera not found, refresh the scene to set everything up properly" + NO_OBJECTS_BAKE_GROUPS = \ + "You have 'Use Bake Group' turned on, but no objects are inside the corresponding collection" + NO_VALID_PATH_SET = \ + "There is no export path set" + + MAT_SLOTS_WITHOUT_LINKS = \ + "Material slots found without links & will be rendered using the sockets default value" + + MARMOSET_EXPORT_COMPLETE = \ + "Export completed! Opening Marmoset Toolbag..." + MARMOSET_RE_EXPORT_COMPLETE = \ + "Models re-exported! Check Marmoset Toolbag" + + OFFLINE_RENDER_COMPLETE = \ + "Offline render completed!" + EXPORT_COMPLETE = \ + "Export completed!" diff --git a/generic_utils.py b/generic_utils.py deleted file mode 100644 index 2169172..0000000 --- a/generic_utils.py +++ /dev/null @@ -1,198 +0,0 @@ - -import bpy, os, re, bpy.types as types -from .__init__ import bl_info -from inspect import getframeinfo, stack -from .render_setup_utils import get_rendered_objects -from .constants import GlobalVariableConstants as GlobalVarConst -from .constants import ErrorCodeConstants as ErrorCodeConst - - -class OpInfo: - bl_options = {'REGISTER', 'UNDO'} - bl_label = "" - - -class PanelInfo: - bl_category = 'GrabDoc' - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_label = "" - - -class SubPanelInfo: - bl_parent_id = "GRABDOC_PT_view_edit_maps" - bl_options = {'HEADER_LAYOUT_EXPAND', 'DEFAULT_CLOSED'} - - -class UseSelectedOnly(): - @classmethod - def poll(cls, context: types.Context) -> bool: - return True if len(context.selected_objects) else poll_message_error(cls, ErrorCodeConst.NO_OBJECTS_SELECTED) - - -def format_bl_version(bl_version: str=bl_info["version"]) -> str: - tuples_version_pattern = r'\((\d+), (\d+), (\d+)\)' - - match = re.match(tuples_version_pattern, str(bl_version)) - return '.'.join(match.groups()) if match else None - - -def export_bg_plane(context: types.Context) -> None: - """Export the grabdoc background plane for external use""" - grabDoc = context.scene.grabDoc - - # Save original selection - savedSelection = context.selected_objects - - # Deselect all objects - bpy.ops.object.select_all(action='DESELECT') - - # Select bg plane, export and deselect bg plane - bpy.data.collections[GlobalVarConst.COLL_NAME].hide_select = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].hide_select = False - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].select_set(True) - - bpy.ops.export_scene.fbx( - filepath=os.path.join(bpy.path.abspath(grabDoc.exportPath), grabDoc.exportName + '_plane.fbx'), - use_selection=True - ) - - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].select_set(False) - - # Refresh original selection - for ob in savedSelection: - ob.select_set(True) - - if not grabDoc.collSelectable: - bpy.data.collections[GlobalVarConst.COLL_NAME].hide_select = False - - -def proper_scene_setup() -> bool: - """Look for grabdoc objects to decide if the scene is setup correctly""" - object_checks = ( - GlobalVarConst.COLL_NAME in bpy.data.collections, - GlobalVarConst.BG_PLANE_NAME in bpy.context.scene.objects, - GlobalVarConst.TRIM_CAMERA_NAME in bpy.context.scene.objects - ) - return True if all(object_checks) else False - - -def is_camera_in_3d_view() -> bool: - """Check if we are actively viewing through the camera in the 3D View""" - return [area.spaces.active.region_3d.view_perspective for area in bpy.context.screen.areas if area.type == 'VIEW_3D'] == ['CAMERA'] - - -def is_pro_version() -> bool: # NOTE Basic DRM is best DRM - return True if "Pro" in bl_info["name"] else False - - -def get_create_addon_temp_dir(temp_dir_name: str="Temp", create_dir: bool=True) -> tuple[str, str]: - """Creates a temporary files directory for automatically handled I/O""" - addon_path = os.path.dirname(__file__) - temps_path = os.path.join(addon_path, temp_dir_name) - - if create_dir and not os.path.exists(temps_path): - os.mkdir(temps_path) - return (addon_path, temps_path) - - -def get_debug_line_no() -> str: - """Simple method of getting the line number of a particular error""" - caller = getframeinfo(stack()[2][0]) - - file_name = caller.filename.split("\\", -1)[-1] - line_num = caller.lineno - return f"{file_name}:{line_num}" - - -def poll_message_error(cls: types.Operator, error_message: str, print_err_line: bool=True) -> bool: - """Calls the poll_message_set function, for use in operator polls. This ALWAYS returns a False boolean value if called. - - Parameters - ---------- - error_message : str - Error message to print to the user - print_err_line : bool, optional - Wheth, by default True - """ - cls.poll_message_set(f"{error_message}. ({get_debug_line_no()})'" if print_err_line else f"{error_message}.") - return False - - -def get_format_extension() -> str: - """Get the correct file extension based on GrabDocs imageType attribute""" - return f".{GlobalVarConst.FORMAT_MATCHED_EXTENSIONS[bpy.context.scene.grabDoc.imageType]}" - - -def bad_setup_check(self, context: types.Context, active_export: bool, report_value=False, report_string="") -> tuple[bool, str]: - """Determine if specific parts of the scene are set up incorrectly and return a detailed explanation of things for the user to fix""" - grabDoc = context.scene.grabDoc - - # Run this before other error checks as the following error checks contain dependencies - self.rendered_obs = get_rendered_objects(context) - - # Look for Trim Camera (only thing required to render) - if not GlobalVarConst.TRIM_CAMERA_NAME in context.view_layer.objects and not report_value: - report_value = True - report_string = ErrorCodeConst.TRIM_CAM_NOT_FOUND - - # Check for no objects in manual collection - if grabDoc.onlyRenderColl and not report_value: - if not len(bpy.data.collections[GlobalVarConst.COLL_OB_NAME].objects): - report_value = True - report_string = ErrorCodeConst.NO_OBJECTS_BAKE_GROUPS - - if active_export: - # Check for export path - if not os.path.exists(bpy.path.abspath(grabDoc.exportPath)) and not report_value: - report_value = True - report_string = ErrorCodeConst.NO_VALID_PATH_SET - - # Check if all bake maps are disabled - bake_maps = ( - grabDoc.exportNormals, - grabDoc.exportCurvature, - grabDoc.exportOcclusion, - grabDoc.exportHeight, - grabDoc.exportMatID, - grabDoc.exportAlpha, - grabDoc.exportAlbedo, - grabDoc.exportRoughness, - grabDoc.exportMetalness - ) - - bake_map_vis = ( - grabDoc.uiVisibilityNormals, - grabDoc.uiVisibilityCurvature, - grabDoc.uiVisibilityOcclusion, - grabDoc.uiVisibilityHeight, - grabDoc.uiVisibilityMatID, - grabDoc.uiVisibilityAlpha, - grabDoc.uiVisibilityAlbedo, - grabDoc.uiVisibilityRoughness, - grabDoc.uiVisibilityMetalness - ) - - if not True in bake_maps or not True in bake_map_vis: - report_value = True - report_string = "No bake maps are turned on." - - return (report_value, report_string) - - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### diff --git a/img_utils.py b/img_utils.py deleted file mode 100644 index 0bd39a5..0000000 --- a/img_utils.py +++ /dev/null @@ -1,12 +0,0 @@ -import bpy -import numpy as np - - -def get_pixels_from_img(): - pass - - -def remove_channel(idx: int=3): - pass - - diff --git a/marmoset_ops.py b/marmoset_ops.py deleted file mode 100644 index c56d0b0..0000000 --- a/marmoset_ops.py +++ /dev/null @@ -1,212 +0,0 @@ - -import bpy, os, subprocess, json, bpy.types as types -from .generic_utils import OpInfo, bad_setup_check, export_bg_plane, get_create_addon_temp_dir -from .render_setup_utils import find_tallest_object -from .constants import GlobalVariableConstants as GlobalVarConst -from .constants import ErrorCodeConstants as ErrorCodeConst - - -################################################################################################################ -# MARMOSET EXPORTER -################################################################################################################ - - -class GrabDoc_OT_send_to_marmo(OpInfo, types.Operator): - """Export your models, open & bake (if turned on) in Marmoset Toolbag utilizing the settings set within the 'View / Edit Maps' tab""" - bl_idname = "grab_doc.bake_marmoset" - bl_label = "Open / Refresh in Marmoset" - - send_type: bpy.props.EnumProperty( - items=( - ('open',"Open",""), - ('refresh', "Refresh", "") - ), - options={'HIDDEN'} - ) - - @classmethod - def poll(cls, context: types.Context) -> bool: - return os.path.exists(context.preferences.addons[__package__].preferences.marmoEXE) - - def open_marmoset(self, context: types.Context, temps_path, addon_path): - grabDoc = context.scene.grabDoc - marmo_exe = context.preferences.addons[__package__].preferences.marmoEXE - - # Create a dictionary of variables to transfer into Marmoset - marmo_vars = { - 'file_path': f'{bpy.path.abspath(grabDoc.exportPath)}{grabDoc.exportName}.{grabDoc.imageType_marmo.lower()}', - 'file_ext': grabDoc.imageType_marmo.lower(), - 'file_path_no_ext': bpy.path.abspath(grabDoc.exportPath), - 'marmo_sky_path': f'{os.path.dirname(marmo_exe)}\\data\\sky\\Evening Clouds.tbsky', - - 'resolution_x': grabDoc.exportResX, - 'resolution_y': grabDoc.exportResY, - 'bits_per_channel': int(grabDoc.colorDepth), - 'samples': int(grabDoc.marmoSamples), - - 'auto_bake': grabDoc.marmoAutoBake, - 'close_after_bake': grabDoc.marmoClosePostBake, - 'open_folder': grabDoc.openFolderOnExport, - - 'export_normal': grabDoc.exportNormals & grabDoc.uiVisibilityNormals, - 'flipy_normal': grabDoc.flipYNormals, - 'suffix_normal': grabDoc.suffixNormals, - - 'export_curvature': grabDoc.exportCurvature & grabDoc.uiVisibilityCurvature, - 'suffix_curvature': grabDoc.suffixCurvature, - - 'export_occlusion': grabDoc.exportOcclusion & grabDoc.uiVisibilityOcclusion, - 'ray_count_occlusion': grabDoc.marmoAORayCount, - 'suffix_occlusion': grabDoc.suffixOcclusion, - - 'export_height': grabDoc.exportHeight & grabDoc.uiVisibilityHeight, - 'cage_height': grabDoc.guideHeight * 100 * 2, - 'suffix_height': grabDoc.suffixHeight, - - 'export_alpha': grabDoc.exportAlpha & grabDoc.uiVisibilityAlpha, - 'suffix_alpha': grabDoc.suffixAlpha, - - 'export_matid': grabDoc.exportMatID & grabDoc.uiVisibilityMatID, - 'suffix_id': grabDoc.suffixID - } - - # Flip the slashes of the first Dict value (It's gross but I don't know how to do it any other way without an error in Marmoset) - for key, value in marmo_vars.items(): - marmo_vars[key] = value.replace("\\", "/") - break - - # Serializing - marmo_json = json.dumps(marmo_vars, indent = 4) - - # Writing - with open(os.path.join(temps_path, "marmo_vars.json"), "w") as outfile: - outfile.write(marmo_json) - - path_ext_only = os.path.basename(os.path.normpath(marmo_exe)).encode() - - if grabDoc.exportPlane: - export_bg_plane(context) - - subproc_args = [ - marmo_exe, - os.path.join(addon_path, "marmoset_utils.py") - ] - - if self.send_type == 'refresh': - sub_proc = subprocess.check_output('tasklist', shell=True) # TODO don't use shell=True arg - - if not path_ext_only in sub_proc: - subprocess.Popen(subproc_args) - - self.report({'INFO'}, ErrorCodeConst.MARMO_EXPORT_COMPLETE) - else: - self.report({'INFO'}, ErrorCodeConst.MARMO_RE_EXPORT_COMPLETE) - else: - subprocess.Popen(subproc_args) - - self.report({'INFO'}, ErrorCodeConst.MARMO_EXPORT_COMPLETE) - return {'FINISHED'} - - def execute(self, context: types.Context): - grabDoc = context.scene.grabDoc - - report_value, report_string = bad_setup_check(self, context, active_export=True) - if report_value: - self.report({'ERROR'}, report_string) - return {'CANCELLED'} - - addon_path, temps_path = get_create_addon_temp_dir() - - saved_selected = context.view_layer.objects.selected.keys() - - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') - - if grabDoc.exportHeight and grabDoc.rangeTypeHeight == 'AUTO': - find_tallest_object(self, context) - - # Set high poly naming - for ob in context.view_layer.objects: - ob.select_set(False) - - if ob.name in self.rendered_obs and ob.visible_get() and ob.name != GlobalVarConst.BG_PLANE_NAME: - ob.select_set(True) - - ob.name = f"{GlobalVarConst.GD_HIGH_PREFIX} {ob.name}" - - # Get background plane low and high poly - bg_plane_ob = bpy.data.objects.get(GlobalVarConst.BG_PLANE_NAME) - bg_plane_ob.name = f"{GlobalVarConst.GD_LOW_PREFIX} {GlobalVarConst.BG_PLANE_NAME}" - bpy.data.collections[GlobalVarConst.COLL_NAME].hide_select = bg_plane_ob.hide_select = False - bg_plane_ob.select_set(True) - - # Copy the object, link into the scene & rename as high poly - bg_plane_ob_copy = bg_plane_ob.copy() - context.collection.objects.link(bg_plane_ob_copy) - bg_plane_ob_copy.name = f"{GlobalVarConst.GD_HIGH_PREFIX} {GlobalVarConst.BG_PLANE_NAME}" - bg_plane_ob_copy.select_set(True) - - # Remove reference material - if GlobalVarConst.REFERENCE_NAME in bpy.data.materials: - bpy.data.materials.remove(bpy.data.materials.get(GlobalVarConst.REFERENCE_NAME)) - - # Export models - bpy.ops.export_scene.fbx( - filepath=f"{temps_path}\\GD_temp_model.fbx", - use_selection=True, - path_mode='ABSOLUTE' - ) - - bpy.data.objects.remove(bg_plane_ob_copy) # TODO remove mesh instead? Verify this doesn't leave floating data - - for ob in context.selected_objects: - ob.select_set(False) - - if ob.name == f"{GlobalVarConst.GD_LOW_PREFIX} {GlobalVarConst.BG_PLANE_NAME}": - ob.name = GlobalVarConst.BG_PLANE_NAME - else: - ob.name = ob.name[8:] # TODO what does this represent? - - if not grabDoc.collSelectable: - bpy.data.collections[GlobalVarConst.COLL_NAME].hide_select = True - - for ob_name in saved_selected: - ob = context.scene.objects.get(ob_name) - - if ob.visible_get(): - ob.select_set(True) - - self.open_marmoset(context, temps_path, addon_path) - return {'FINISHED'} - - -################################################################################################################ -# REGISTRATION -################################################################################################################ - - -def register(): - bpy.utils.register_class(GrabDoc_OT_send_to_marmo) - - -def unregister(): - bpy.utils.unregister_class(GrabDoc_OT_send_to_marmo) - - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### diff --git a/node_group_utils.py b/node_group_utils.py deleted file mode 100644 index b7e10b8..0000000 --- a/node_group_utils.py +++ /dev/null @@ -1,523 +0,0 @@ - -import bpy, bpy.types as types -from .constants import GlobalVariableConstants as GlobalVarConst -from .constants import ErrorCodeConstants as ErrorCodeConst - - -def ng_setup() -> None: - """Initial setup of all node groups when setting up a file""" - grabDoc = bpy.context.scene.grabDoc - - # Create a dummy material output node to harvest potential inputs - ng_dummy_output = bpy.data.node_groups.new('Material Output', 'ShaderNodeTree') - output_node = ng_dummy_output.nodes.new('ShaderNodeOutputMaterial') - - collected_inputs = {} - for input in output_node.inputs: - if input.name == 'Thickness': # NOTE I have no idea why this secret input exists - continue - - collected_inputs[input.name] = f'NodeSocket{input.type.capitalize()}' - - # NORMALS - if not GlobalVarConst.NG_NORMAL_NAME in bpy.data.node_groups: - ng_normal = bpy.data.node_groups.new(GlobalVarConst.NG_NORMAL_NAME, 'ShaderNodeTree') - ng_normal.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_normal.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - group_inputs = ng_normal.nodes.new('NodeGroupInput') - group_inputs.name = "Group Input" - group_inputs.location = (-1400,100) - ng_normal.outputs.new('NodeSocketShader', 'Output') - for key, value in collected_inputs.items(): - ng_normal.inputs.new(value, f'Saved {key}') - alpha_input = ng_normal.inputs.new('NodeSocketInt', 'Alpha') - alpha_input.default_value = 1 - ng_normal.inputs.new('NodeSocketVector', 'Normal') - - # Create group nodes - bevel_node = ng_normal.nodes.new('ShaderNodeBevel') - bevel_node.name = "Bevel" - bevel_node.inputs[0].default_value = 0 - bevel_node.location = (-1000,0) - - bevel_node_2 = ng_normal.nodes.new('ShaderNodeBevel') - bevel_node_2.name = "Bevel.001" - bevel_node_2.location = (-1000,-200) - bevel_node_2.inputs[0].default_value = 0 - - vec_transform_node = ng_normal.nodes.new('ShaderNodeVectorTransform') - vec_transform_node.name = "Vector Transform" - vec_transform_node.vector_type = 'NORMAL' - vec_transform_node.convert_to = 'CAMERA' - vec_transform_node.location = (-800,0) - - vec_multiply_node = ng_normal.nodes.new('ShaderNodeVectorMath') - vec_multiply_node.name = "Vector Math" - vec_multiply_node.operation = 'MULTIPLY' - vec_multiply_node.inputs[1].default_value[0] = .5 - vec_multiply_node.inputs[1].default_value[1] = -.5 if grabDoc.flipYNormals else .5 - vec_multiply_node.inputs[1].default_value[2] = -.5 - vec_multiply_node.location = (-600,0) - - vec_add_node = ng_normal.nodes.new('ShaderNodeVectorMath') - vec_add_node.name = "Vector Math.001" - vec_add_node.inputs[1].default_value[0] = vec_add_node.inputs[1].default_value[1] = vec_add_node.inputs[1].default_value[2] = 0.5 - vec_add_node.location = (-400,0) - - invert_node = ng_normal.nodes.new('ShaderNodeInvert') - invert_node.name = "Invert" - invert_node.location = (-1000,200) - - subtract_node = ng_normal.nodes.new('ShaderNodeMixRGB') - subtract_node.blend_type = 'SUBTRACT' - subtract_node.name = "Subtract" - subtract_node.inputs[0].default_value = 1 - subtract_node.inputs[1].default_value = (1, 1, 1, 1) - subtract_node.location = (-800,300) - - transp_shader_node = ng_normal.nodes.new('ShaderNodeBsdfTransparent') - transp_shader_node.name = "Transparent BSDF" - transp_shader_node.location = (-400,200) - - mix_shader_node = ng_normal.nodes.new('ShaderNodeMixShader') - mix_shader_node.name = "Mix Shader" - mix_shader_node.location = (-200,300) - - # Link nodes - link = ng_normal.links - - # Path 1 - link.new(bevel_node.inputs["Normal"], group_inputs.outputs["Normal"]) - link.new(vec_transform_node.inputs["Vector"], bevel_node_2.outputs["Normal"]) - link.new(vec_multiply_node.inputs["Vector"], vec_transform_node.outputs["Vector"]) - link.new(vec_add_node.inputs["Vector"], vec_multiply_node.outputs["Vector"]) - link.new(group_outputs.inputs["Output"], vec_add_node.outputs["Vector"]) - - # Path 2 - link.new(invert_node.inputs['Color'], group_inputs.outputs['Alpha']) - link.new(subtract_node.inputs['Color2'], invert_node.outputs['Color']) - link.new(mix_shader_node.inputs['Fac'], subtract_node.outputs['Color']) - link.new(mix_shader_node.inputs[1], transp_shader_node.outputs['BSDF']) - link.new(mix_shader_node.inputs[2], vec_add_node.outputs['Vector']) - - # AMBIENT OCCLUSION - if not GlobalVarConst.NG_AO_NAME in bpy.data.node_groups: - ng_ao = bpy.data.node_groups.new(GlobalVarConst.NG_AO_NAME, 'ShaderNodeTree') - ng_ao.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_ao.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - ng_ao.outputs.new('NodeSocketShader','Output') - for key, value in collected_inputs.items(): - ng_ao.inputs.new(value, f'Saved {key}') - - # Create group nodes - ao_node = ng_ao.nodes.new('ShaderNodeAmbientOcclusion') - ao_node.name = "Ambient Occlusion" - ao_node.samples = 32 - ao_node.location = (-600,0) - - gamma_node = ng_ao.nodes.new('ShaderNodeGamma') - gamma_node.name = "Gamma" - gamma_node.inputs[1].default_value = grabDoc.gammaOcclusion - gamma_node.location = (-400,0) - - emission_node = ng_ao.nodes.new('ShaderNodeEmission') - emission_node.name = "Emission" - emission_node.location = (-200,0) - - # Link nodes - link = ng_ao.links - link.new(gamma_node.inputs["Color"], ao_node.outputs["Color"]) - link.new(emission_node.inputs["Color"], gamma_node.outputs["Color"]) - link.new(group_outputs.inputs["Output"], emission_node.outputs["Emission"]) - - # HEIGHT - if not GlobalVarConst.NG_HEIGHT_NAME in bpy.data.node_groups: - ng_height = bpy.data.node_groups.new(GlobalVarConst.NG_HEIGHT_NAME, 'ShaderNodeTree') - ng_height.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_height.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - ng_height.outputs.new('NodeSocketShader','Output') - for key, value in collected_inputs.items(): - ng_height.inputs.new(value, f'Saved {key}') - - # Create group nodes - camera_data_node = ng_height.nodes.new('ShaderNodeCameraData') - camera_data_node.name = "Camera Data" - camera_data_node.location = (-800,0) - - map_range_node = ng_height.nodes.new('ShaderNodeMapRange') # Map Range updates handled on map preview - map_range_node.name = "Map Range" - map_range_node.location = (-600,0) - - ramp_node = ng_height.nodes.new('ShaderNodeValToRGB') - ramp_node.name = "ColorRamp" - ramp_node.color_ramp.elements[0].color = (1, 1, 1, 1) - ramp_node.color_ramp.elements[1].color = (0, 0, 0, 1) - ramp_node.location = (-400,0) - - # Link nodes - link = ng_height.links - link.new(map_range_node.inputs["Value"], camera_data_node.outputs["View Z Depth"]) - link.new(ramp_node.inputs["Fac"], map_range_node.outputs["Result"]) - link.new(group_outputs.inputs["Output"], ramp_node.outputs["Color"]) - - # ALPHA - if not GlobalVarConst.NG_ALPHA_NAME in bpy.data.node_groups: - ng_alpha = bpy.data.node_groups.new(GlobalVarConst.NG_ALPHA_NAME, 'ShaderNodeTree') - ng_alpha.use_fake_user = True - - # Create group input/outputs - group_outputs = ng_alpha.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - ng_alpha.outputs.new('NodeSocketShader','Output') - for key, value in collected_inputs.items(): - ng_alpha.inputs.new(value, f'Saved {key}') - - # Create group nodes - camera_data_node = ng_alpha.nodes.new('ShaderNodeCameraData') - camera_data_node.name = "Camera Data" - camera_data_node.location = (-800,0) - - gd_camera_ob_z = bpy.data.objects.get(GlobalVarConst.TRIM_CAMERA_NAME).location[2] - - map_range_node = ng_alpha.nodes.new('ShaderNodeMapRange') - map_range_node.name = "Map Range" - map_range_node.location = (-600,0) - map_range_node.inputs[1].default_value = gd_camera_ob_z - .00001 - map_range_node.inputs[2].default_value = gd_camera_ob_z - - invert_node = ng_alpha.nodes.new('ShaderNodeInvert') - invert_node.name = "Invert" - invert_node.location = (-400,0) - - emission_node = ng_alpha.nodes.new('ShaderNodeEmission') - emission_node.name = "Emission" - emission_node.location = (-200,0) - - # Link nodes - link = ng_alpha.links - link.new(map_range_node.inputs["Value"], camera_data_node.outputs["View Z Depth"]) - link.new(invert_node.inputs["Color"], map_range_node.outputs["Result"]) - link.new(emission_node.inputs["Color"], invert_node.outputs["Color"]) - link.new(group_outputs.inputs["Output"], emission_node.outputs["Emission"]) - - # ALBEDO - if not GlobalVarConst.NG_ALBEDO_NAME in bpy.data.node_groups: - ng_albedo = bpy.data.node_groups.new(GlobalVarConst.NG_ALBEDO_NAME, 'ShaderNodeTree') - ng_albedo.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_albedo.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - group_inputs = ng_albedo.nodes.new('NodeGroupInput') - group_inputs.name = "Group Input" - group_inputs.location = (-400,0) - ng_albedo.outputs.new('NodeSocketShader','Output') - ng_albedo.inputs.new('NodeSocketColor', 'Color Input') - for key, value in collected_inputs.items(): - ng_albedo.inputs.new(value, f'Saved {key}') - - emission_node = ng_albedo.nodes.new('ShaderNodeEmission') - emission_node.name = "Emission" - emission_node.location = (-200,0) - - # Link nodes - link = ng_albedo.links - link.new(emission_node.inputs["Color"], group_inputs.outputs["Color Input"]) - link.new(group_outputs.inputs["Output"], emission_node.outputs["Emission"]) - - # ROUGHNESS - if not GlobalVarConst.NG_ROUGHNESS_NAME in bpy.data.node_groups: - ng_roughness = bpy.data.node_groups.new(GlobalVarConst.NG_ROUGHNESS_NAME, 'ShaderNodeTree') - ng_roughness.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_roughness.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - group_inputs = ng_roughness.nodes.new('NodeGroupInput') - group_inputs.name = "Group Input" - group_inputs.location = (-600,0) - ng_roughness.outputs.new('NodeSocketShader','Output') - ng_roughness.inputs.new('NodeSocketFloat', 'Roughness Input') - for key, value in collected_inputs.items(): - ng_roughness.inputs.new(value, f'Saved {key}') - - invert_node = ng_roughness.nodes.new('ShaderNodeInvert') - invert_node.location = (-400,0) - invert_node.inputs[0].default_value = 0 - - emission_node = ng_roughness.nodes.new('ShaderNodeEmission') - emission_node.name = "Emission" - emission_node.location = (-200,0) - - # Link nodes - link = ng_roughness.links - link.new(invert_node.inputs["Color"], group_inputs.outputs["Roughness Input"]) - link.new(emission_node.inputs["Color"], invert_node.outputs["Color"]) - link.new(group_outputs.inputs["Output"], emission_node.outputs["Emission"]) - - # METALNESS - if not GlobalVarConst.NG_METALNESS_NAME in bpy.data.node_groups: - ng_roughness = bpy.data.node_groups.new(GlobalVarConst.NG_METALNESS_NAME, 'ShaderNodeTree') - ng_roughness.use_fake_user = True - - # Create group inputs/outputs - group_outputs = ng_roughness.nodes.new('NodeGroupOutput') - group_outputs.name = "Group Output" - group_inputs = ng_roughness.nodes.new('NodeGroupInput') - group_inputs.name = "Group Input" - group_inputs.location = (-400,0) - ng_roughness.outputs.new('NodeSocketShader','Output') - ng_roughness.inputs.new('NodeSocketFloat', 'Metalness Input') - for key, value in collected_inputs.items(): - ng_roughness.inputs.new(value, f'Saved {key}') - - emission_node = ng_roughness.nodes.new('ShaderNodeEmission') - emission_node.name = "Emission" - emission_node.location = (-200,0) - - # Link nodes - link = ng_roughness.links - link.new(emission_node.inputs["Color"], group_inputs.outputs["Metalness Input"]) - link.new(group_outputs.inputs["Output"], emission_node.outputs["Emission"]) - - -def create_apply_ng_mat(ob: types.Object) -> None: - """Create & apply a material to objects without active materials""" - mat_name = GlobalVarConst.GD_MATERIAL_NAME - - # Reuse GrabDoc created material if it already exists - if mat_name in bpy.data.materials: - mat = bpy.data.materials[mat_name] - else: - mat = bpy.data.materials.new(name = mat_name) - mat.use_nodes = True - - # Apply the material to the appropriate slot - # - # Search through all material slots, if any slots have no name that means they have no material - # & therefore should have one added. While this does run every time this is called it should - # only really need to be used once per in add-on use. - # - # We do not want to remove empty material slots as they can be used for masking off materials. - for slot in ob.material_slots: - if slot.name == '': - ob.material_slots[slot.name].material = mat - else: - if not ob.active_material or ob.active_material.name == '': - ob.active_material = mat - - -def bsdf_link_factory(input_name: list, node_group: types.ShaderNodeGroup, original_node_input: types.NodeSocket, mat_slot: types.Material) -> bool: - """Add node group to all materials, save original links & link the node group to material output""" - node_found = False - - for link in original_node_input.links: - node_found = True - - mat_slot.node_tree.links.new(node_group.inputs[input_name], link.from_node.outputs[link.from_socket.name]) - break - else: - try: - node_group.inputs[input_name].default_value = original_node_input.default_value - except TypeError: - if isinstance(original_node_input.default_value, float): - node_group.inputs[input_name].default_value = int(original_node_input.default_value) - else: - node_group.inputs[input_name].default_value = float(original_node_input.default_value) - - return node_found - - -def add_ng_to_mat(self, context: types.Context, setup_type: str) -> None: - """Add corresponding node groups to all materials/objects""" - for ob in context.view_layer.objects: - if ob.name not in self.rendered_obs and ob.name == GlobalVarConst.ORIENT_GUIDE_NAME: - continue - - # If no material slots found or empty mat slots found, assign a material to it - if not len(ob.material_slots) or '' in ob.material_slots: - create_apply_ng_mat(ob) - - # Cycle through all material slots - for slot in ob.material_slots: - mat_slot = bpy.data.materials.get(slot.name) - mat_slot.use_nodes = True - - if setup_type in mat_slot.node_tree.nodes: - continue - - # Get materials Output Material node(s) - output_nodes = {mat_node for mat_node in mat_slot.node_tree.nodes if mat_node.type == 'OUTPUT_MATERIAL'} - if not len(output_nodes): - output_nodes.append(mat_slot.node_tree.nodes.new('ShaderNodeOutputMaterial')) - - for output_node in output_nodes: - # Add node group to material - GD_node_group = mat_slot.node_tree.nodes.new('ShaderNodeGroup') - GD_node_group.node_tree = bpy.data.node_groups[setup_type] - GD_node_group.location = (output_node.location[0], output_node.location[1] - 160) - GD_node_group.name = bpy.data.node_groups[setup_type].name - GD_node_group.hide = True - - # Add note next to node group explaining basic functionality - GD_text = bpy.data.texts.get('_grabdoc_ng_warning') - if GD_text is None: - GD_text = bpy.data.texts.new(name='_grabdoc_ng_warning') - - GD_text.clear() - GD_text.write(GlobalVarConst.NG_NODE_WARNING) - - GD_frame = mat_slot.node_tree.nodes.new('NodeFrame') - GD_frame.location = (output_node.location[0], output_node.location[1] - 195) - GD_frame.name = bpy.data.node_groups[setup_type].name - GD_frame.text = GD_text - GD_frame.width = 1000 - GD_frame.height = 150 - - # Handle node linking - for node_input in output_node.inputs: - for link in node_input.links: - original_node = mat_slot.node_tree.nodes.get(link.from_node.name) - - # Link original connections to the Node Group - connections_to_make = ('Surface', 'Volume', 'Displacement') # TODO can be more modular - if node_input.name in connections_to_make: - for connection_name in connections_to_make: - if node_input.name == connection_name: - mat_slot.node_tree.links.new( - GD_node_group.inputs[f"Saved {connection_name}"], - original_node.outputs[link.from_socket.name] - ) - - # Links for maps that feed information from the Principled BSDF - if setup_type in ( - GlobalVarConst.NG_ALBEDO_NAME, - GlobalVarConst.NG_ROUGHNESS_NAME, - GlobalVarConst.NG_METALNESS_NAME, - GlobalVarConst.NG_NORMAL_NAME - ) and original_node.type == 'BSDF_PRINCIPLED': - node_found = False - - for original_node_input in original_node.inputs: - if setup_type == GlobalVarConst.NG_ALBEDO_NAME and original_node_input.name == 'Base Color': - node_found = bsdf_link_factory( - input_name='Color Input', - node_group=GD_node_group, - original_node_input=original_node_input, - mat_slot=mat_slot - ) - elif setup_type == GlobalVarConst.NG_ROUGHNESS_NAME and original_node_input.name == 'Roughness': - node_found = bsdf_link_factory( - input_name='Roughness Input', - node_group=GD_node_group, - original_node_input=original_node_input, - mat_slot=mat_slot - ) - elif setup_type == GlobalVarConst.NG_METALNESS_NAME and original_node_input.name == 'Metallic': - node_found = bsdf_link_factory( - input_name='Metalness Input', - node_group=GD_node_group, - original_node_input=original_node_input, - mat_slot=mat_slot - ) - elif setup_type == GlobalVarConst.NG_NORMAL_NAME and original_node_input.name in ('Normal', 'Alpha'): - node_found = bsdf_link_factory( - input_name=original_node_input.name, - node_group=GD_node_group, - original_node_input=original_node_input, - mat_slot=mat_slot - ) - - if ( # Does not work if Map Preview Mode is entered and *then* Texture Normals are enabled - original_node_input.name == 'Alpha' - and context.scene.grabDoc.useTextureNormals - and mat_slot.blend_method == 'OPAQUE' - and len(original_node_input.links) - ): - mat_slot.blend_method = 'CLIP' - elif node_found: - break - - if not node_found and setup_type != GlobalVarConst.NG_NORMAL_NAME and mat_slot.name != GlobalVarConst.GD_MATERIAL_NAME: - self.report({'WARNING'}, ErrorCodeConst.MAT_SLOTS_WITHOUT_LINKS) - - for link in output_node.inputs['Volume'].links: - mat_slot.node_tree.links.remove(link) - for link in output_node.inputs['Displacement'].links: - mat_slot.node_tree.links.remove(link) - - # Link Node Group to the output - mat_slot.node_tree.links.new(output_node.inputs["Surface"], GD_node_group.outputs["Output"]) - - -def cleanup_ng_from_mat(setup_type: str) -> None: - """Remove node group & return original links if they exist""" - for mat in bpy.data.materials: - mat.use_nodes = True - - # If there is a GrabDoc created material, remove it - if mat.name == GlobalVarConst.GD_MATERIAL_NAME: - bpy.data.materials.remove(mat) - continue - elif setup_type not in mat.node_tree.nodes: - continue - - # If a material has a GrabDoc created Node Group, remove it - GD_node_groups = [mat_node for mat_node in mat.node_tree.nodes if mat_node.name.startswith(setup_type)] - for GD_node_group in GD_node_groups: - output_node = None - for output in GD_node_group.outputs: - for link in output.links: - if link.to_node.type == 'OUTPUT_MATERIAL': - output_node = link.to_node - break - if output_node is not None: - break - - if output_node is None: - mat.node_tree.nodes.remove(GD_node_group) - continue - - for input in GD_node_group.inputs: - for link in input.links: - original_node_connection = mat.node_tree.nodes.get(link.from_node.name) - original_node_socket = link.from_socket.name - - connections_to_make = ('Surface', 'Volume', 'Displacement') # TODO can be more modular - if input.name.split(' ')[-1] in connections_to_make: - for connection_name in connections_to_make: - if input.name == f'Saved {connection_name}': - mat.node_tree.links.new( - output_node.inputs[connection_name], - original_node_connection.outputs[original_node_socket] - ) - - mat.node_tree.nodes.remove(GD_node_group) - - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### diff --git a/operators/__init__.py b/operators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/operators/marmoset.py b/operators/marmoset.py new file mode 100644 index 0000000..34cac54 --- /dev/null +++ b/operators/marmoset.py @@ -0,0 +1,236 @@ + +import os +import subprocess +import json + +import bpy +from bpy.types import Context, Operator + +from .operators import OpInfo +from ..constants import GlobalVariableConstants as Global +from ..constants import ErrorCodeConstants as Error +from ..utils.generic import ( + bad_setup_check, + export_bg_plane, + get_create_addon_temp_dir +) +from ..utils.render import set_guide_height + + +################################################ +# MARMOSET EXPORTER +################################################ + + +class GrabDoc_OT_send_to_marmo(OpInfo, Operator): + """Export your models, open & bake (if turned on) in + Marmoset Toolbag utilizing the settings set within + the 'View / Edit Maps' tab""" + bl_idname = "grab_doc.bake_marmoset" + bl_label = "Open / Refresh in Marmoset" + + send_type: bpy.props.EnumProperty( + items=( + ('open',"Open",""), + ('refresh', "Refresh", "") + ), + options={'HIDDEN'} + ) + + @classmethod + def poll(cls, context: Context) -> bool: + return os.path.exists( + context.preferences.addons[__package__].preferences.marmoEXE + ) + + def open_marmoset(self, context: Context, temps_path, addon_path): + gd = context.scene.grabDoc + marmo_exe = context.preferences.addons[__package__].preferences.marmoEXE + + # Create a dictionary of variables to transfer into Marmoset + marmo_vars = { + 'file_path': f'{bpy.path.abspath(gd.exportPath)}{gd.exportName}.{gd.imageType_marmo.lower()}', + 'file_ext': gd.imageType_marmo.lower(), + 'file_path_no_ext': bpy.path.abspath(gd.exportPath), + 'marmo_sky_path': f'{os.path.dirname(marmo_exe)}\\data\\sky\\Evening Clouds.tbsky', + + 'resolution_x': gd.exportResX, + 'resolution_y': gd.exportResY, + 'bits_per_channel': int(gd.colorDepth), + 'samples': int(gd.marmoSamples), + + 'auto_bake': gd.marmoAutoBake, + 'close_after_bake': gd.marmoClosePostBake, + 'open_folder': gd.openFolderOnExport, + + 'export_normal': gd.exportNormals & gd.uiVisibilityNormals, + 'flipy_normal': gd.flipYNormals, + 'suffix_normal': gd.suffixNormals, + + 'export_curvature': gd.exportCurvature & gd.uiVisibilityCurvature, + 'suffix_curvature': gd.suffixCurvature, + + 'export_occlusion': gd.exportOcclusion & gd.uiVisibilityOcclusion, + 'ray_count_occlusion': gd.marmoAORayCount, + 'suffix_occlusion': gd.suffixOcclusion, + + 'export_height': gd.exportHeight & gd.uiVisibilityHeight, + 'cage_height': gd.guideHeight * 100 * 2, + 'suffix_height': gd.suffixHeight, + + 'export_alpha': gd.exportAlpha & gd.uiVisibilityAlpha, + 'suffix_alpha': gd.suffixAlpha, + + 'export_matid': gd.exportMatID & gd.uiVisibilityMatID, + 'suffix_id': gd.suffixID + } + + # Flip the slashes of the first Dict value (It's + # gross but I don't know how to do it any other + # way without an error in Marmoset) + for key, value in marmo_vars.items(): + marmo_vars[key] = value.replace("\\", "/") + break + + # Serializing + marmo_json = json.dumps(marmo_vars, indent = 4) + + # Writing + with open( + os.path.join(temps_path, "marmo_vars.json"), "w", encoding="utf-8" + ) as outfile: + outfile.write(marmo_json) + + path_ext_only = os.path.basename(os.path.normpath(marmo_exe)).encode() + + if gd.exportPlane: + export_bg_plane(context) + + subproc_args = [ + marmo_exe, + os.path.join(addon_path, "marmoset_utils.py") + ] + + if self.send_type == 'refresh': + # TODO: don't use shell=True arg + sub_proc = subprocess.check_output('tasklist', shell=True) + + if not path_ext_only in sub_proc: + subprocess.Popen(subproc_args) + + self.report({'INFO'}, Error.MARMOSET_EXPORT_COMPLETE) + else: + self.report({'INFO'}, Error.MARMOSET_RE_EXPORT_COMPLETE) + else: + subprocess.Popen(subproc_args) + + self.report({'INFO'}, Error.MARMOSET_EXPORT_COMPLETE) + return {'FINISHED'} + + def execute(self, context: Context): + gd = context.scene.grabDoc + + report_value, report_string = bad_setup_check(self, context, active_export=True) + if report_value: + self.report({'ERROR'}, report_string) + return {'CANCELLED'} + + addon_path, temps_path = get_create_addon_temp_dir() + + saved_selected = context.view_layer.objects.selected.keys() + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='OBJECT') + + if gd.exportHeight and gd.rangeTypeHeight == 'AUTO': + set_guide_height() + + # Set high poly naming + for ob in context.view_layer.objects: + ob.select_set(False) + + if ob.name in self.rendered_obs \ + and ob.visible_get() and ob.name != Global.BG_PLANE_NAME: + ob.select_set(True) + + ob.name = f"{Global.GD_HIGH_PREFIX} {ob.name}" + + # Get background plane low and high poly + bg_plane_ob = bpy.data.objects.get(Global.BG_PLANE_NAME) + bg_plane_ob.name = f"{Global.GD_LOW_PREFIX} {Global.BG_PLANE_NAME}" + bpy.data.collections[Global.COLL_NAME].hide_select = \ + bg_plane_ob.hide_select = False + bg_plane_ob.select_set(True) + + # Copy the object, link into the scene & rename as high poly + bg_plane_ob_copy = bg_plane_ob.copy() + context.collection.objects.link(bg_plane_ob_copy) + bg_plane_ob_copy.name = f"{Global.GD_HIGH_PREFIX} {Global.BG_PLANE_NAME}" + bg_plane_ob_copy.select_set(True) + + # Remove reference material + if Global.REFERENCE_NAME in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials.get(Global.REFERENCE_NAME)) + + # Export models + bpy.ops.export_scene.fbx( + filepath=f"{temps_path}\\GD_temp_model.fbx", + use_selection=True, + path_mode='ABSOLUTE' + ) + + # TODO: remove mesh instead? Verify + # this doesn't leave floating data + bpy.data.objects.remove(bg_plane_ob_copy) + + for ob in context.selected_objects: + ob.select_set(False) + + if ob.name == f"{Global.GD_LOW_PREFIX} {Global.BG_PLANE_NAME}": + ob.name = Global.BG_PLANE_NAME + else: + ob.name = ob.name[8:] # TODO: what does this represent? + + if not gd.collSelectable: + bpy.data.collections[Global.COLL_NAME].hide_select = True + + for ob_name in saved_selected: + ob = context.scene.objects.get(ob_name) + + if ob.visible_get(): + ob.select_set(True) + + self.open_marmoset(context, temps_path, addon_path) + return {'FINISHED'} + + +################################################ +# REGISTRATION +################################################ + + +def register(): + bpy.utils.register_class(GrabDoc_OT_send_to_marmo) + + +def unregister(): + bpy.utils.unregister_class(GrabDoc_OT_send_to_marmo) + + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### diff --git a/mat_id_ops.py b/operators/material.py similarity index 67% rename from mat_id_ops.py rename to operators/material.py index 463419b..4a6fc2f 100644 --- a/mat_id_ops.py +++ b/operators/material.py @@ -1,12 +1,14 @@ - -import bpy, bpy.types as types from random import random, randint -from .generic_utils import OpInfo, UseSelectedOnly -from .render_setup_utils import get_rendered_objects -from .constants import GlobalVariableConstants as GlobalVarConst + +import bpy +from bpy.types import Context, Operator + +from .operators import OpInfo +from ..constants import GlobalVariableConstants as Global +from ..utils.generic import UseSelectedOnly, get_rendered_objects -def generate_random_id_name(id_prefix: str='ID') -> str: +def generate_random_id(id_prefix: str='ID') -> str: """Generates a random id map name based on a given prefix""" while True: new_mat_name = f"{id_prefix}.{randint(1000, 100000)}" @@ -15,58 +17,61 @@ def generate_random_id_name(id_prefix: str='ID') -> str: return new_mat_name -class GRABDOC_OT_quick_id_setup(OpInfo, types.Operator): +class GRABDOC_OT_quick_id_setup(OpInfo, Operator): """Sets up materials on all objects within the cameras view frustrum""" bl_idname = "grab_doc.quick_id_setup" bl_label = "Auto ID Full Scene" - def execute(self, context: types.Context): + def execute(self, context: Context): for mat in bpy.data.materials: - if mat.name.startswith(GlobalVarConst.MAT_ID_RAND_PREFIX): + if mat.name.startswith(Global.MAT_ID_RAND_PREFIX): bpy.data.materials.remove(mat) self.rendered_obs = get_rendered_objects(context) for ob in context.view_layer.objects: add_mat = True - if not ob.name.startswith(GlobalVarConst.GD_PREFIX) and ob.name in self.rendered_obs: + if not ob.name.startswith(Global.GD_PREFIX) \ + and ob.name in self.rendered_obs: for slot in ob.material_slots: # If a manual ID exists on the object, ignore it - if slot.name.startswith(GlobalVarConst.MAT_ID_PREFIX): + if slot.name.startswith(Global.MAT_ID_PREFIX): add_mat = False break if not add_mat: continue - mat = bpy.data.materials.new(generate_random_id_name(GlobalVarConst.MAT_ID_RAND_PREFIX)) + mat = bpy.data.materials.new( + generate_random_id(Global.MAT_ID_RAND_PREFIX) + ) mat.use_nodes = True mat.diffuse_color = (random(), random(), random(), 1) # Viewport color - bsdf_node = mat.node_tree.nodes.get('Principled BSDF') - bsdf_node.inputs[0].default_value = mat.diffuse_color + bsdf = mat.node_tree.nodes.get('Principled BSDF') + bsdf.inputs[0].default_value = mat.diffuse_color ob.active_material_index = 0 ob.active_material = mat for mat in bpy.data.materials: - if (mat.name.startswith(GlobalVarConst.MAT_ID_PREFIX) or mat.name.startswith(GlobalVarConst.MAT_ID_RAND_PREFIX)) and not mat.users: + if (mat.name.startswith(Global.MAT_ID_PREFIX) or mat.name.startswith(Global.MAT_ID_RAND_PREFIX)) and not mat.users: bpy.data.materials.remove(mat) return {'FINISHED'} -class GRABDOC_OT_quick_id_selected(OpInfo, UseSelectedOnly, types.Operator): +class GRABDOC_OT_quick_id_selected(OpInfo, UseSelectedOnly, Operator): """Adds a new single material with a random color to the selected objects""" bl_idname = "grab_doc.quick_id_selected" bl_label = "Add ID to Selected" - def execute(self, context: types.Context): - mat = bpy.data.materials.new(generate_random_id_name(GlobalVarConst.MAT_ID_PREFIX)) + def execute(self, context: Context): + mat = bpy.data.materials.new(generate_random_id(Global.MAT_ID_PREFIX)) mat.use_nodes = True mat.diffuse_color = (random(), random(), random(), 1) - bsdf_node = mat.node_tree.nodes.get('Principled BSDF') - bsdf_node.inputs[0].default_value = mat.diffuse_color + bsdf = mat.node_tree.nodes.get('Principled BSDF') + bsdf.inputs[0].default_value = mat.diffuse_color for ob in context.selected_objects: if ob.type in ('MESH', 'CURVE'): @@ -74,43 +79,43 @@ def execute(self, context: types.Context): ob.active_material = mat for mat in bpy.data.materials: - if (mat.name.startswith(GlobalVarConst.MAT_ID_PREFIX) or mat.name.startswith(GlobalVarConst.MAT_ID_RAND_PREFIX)) and not mat.users: + if (mat.name.startswith(Global.MAT_ID_PREFIX) or mat.name.startswith(Global.MAT_ID_RAND_PREFIX)) and not mat.users: bpy.data.materials.remove(mat) return {'FINISHED'} -class GRABDOC_OT_remove_mats_by_name(OpInfo, types.Operator): +class GRABDOC_OT_remove_mats_by_name(OpInfo, Operator): """Remove materials based on an internal prefixed name""" bl_idname = "grab_doc.remove_mats_by_name" bl_label = "Remove Mats by Name" mat_name: bpy.props.StringProperty(options={'HIDDEN'}) - def execute(self, _context: types.Context): + def execute(self, _context: Context): for mat in bpy.data.materials: if mat.name.startswith(self.mat_name): bpy.data.materials.remove(mat) return {'FINISHED'} -class GRABDOC_OT_quick_remove_selected_mats(OpInfo, UseSelectedOnly, types.Operator): +class GRABDOC_OT_quick_remove_selected_mats(OpInfo, UseSelectedOnly, Operator): """Remove all GrabDoc ID materials based on the selected objects from the scene""" bl_idname = "grab_doc.quick_remove_selected_mats" bl_label = "Remove Selected Materials" - def execute(self, context: types.Context): + def execute(self, context: Context): for ob in context.selected_objects: if ob.type in ('MESH', 'CURVE'): for slot in ob.material_slots: - if slot.name.startswith(GlobalVarConst.MAT_ID_PREFIX): + if slot.name.startswith(Global.MAT_ID_PREFIX): bpy.data.materials.remove(bpy.data.materials[slot.name]) break return {'FINISHED'} -################################################################################################################ +################################################ # REGISTRATION -################################################################################################################ +################################################ classes = ( diff --git a/operators.py b/operators/operators.py similarity index 56% rename from operators.py rename to operators/operators.py index 53c77ea..1f3fc95 100644 --- a/operators.py +++ b/operators/operators.py @@ -1,8 +1,33 @@ +import os +import time -import bpy, time, os, blf, bpy.types as types +import bpy +import blf +from bpy.types import SpaceView3D, Event, Context, Operator, UILayout from bpy.props import EnumProperty -from .generic_utils import ( - OpInfo, + +from ..utils.node import cleanup_ng_from_mat +from ..utils.scene import scene_setup, remove_setup +from ..utils.baker import ( + export_and_preview_setup, + normals_setup, + curvature_setup, + curvature_refresh, + occlusion_setup, + occlusion_refresh, + height_setup, + alpha_setup, + id_setup, + albedo_setup, + roughness_setup, + metalness_setup, + reimport_as_material, + export_refresh +) + +from ..constants import GlobalVariableConstants as Global +from ..constants import ErrorCodeConstants as Error +from ..utils.generic import ( proper_scene_setup, bad_setup_check, export_bg_plane, @@ -11,61 +36,64 @@ get_create_addon_temp_dir, poll_message_error ) -from .node_group_utils import cleanup_ng_from_mat -from .scene_setup_utils import scene_setup, remove_setup -from .baker_setup_cleanup_utils import * -from .constants import GlobalVariableConstants as GlobalVarConst -from .constants import ErrorCodeConstants as ErrorCodeConst -################################################################################################################ +class OpInfo: + bl_options = {'REGISTER', 'UNDO'} + bl_label = "" + + +################################################ # MISC -################################################################################################################ +################################################ -class GRABDOC_OT_load_ref(OpInfo, types.Operator): +class GRABDOC_OT_load_ref(OpInfo, Operator): """Import a reference onto the background plane""" bl_idname = "grab_doc.load_ref" bl_label = "Load Reference" filepath: bpy.props.StringProperty(subtype="FILE_PATH") - def execute(self, context: types.Context): + def execute(self, context: Context): # Load a new image into the main database bpy.data.images.load(self.filepath, check_existing=True) - context.scene.grabDoc.refSelection = bpy.data.images[os.path.basename(os.path.normpath(self.filepath))] + context.scene.grabDoc.refSelection = \ + bpy.data.images[os.path.basename(os.path.normpath(self.filepath))] return {'FINISHED'} - def invoke(self, context: types.Context, _event: types.Event): + def invoke(self, context: Context, _event: Event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} -class GRABDOC_OT_open_folder(OpInfo, types.Operator): +class GRABDOC_OT_open_folder(OpInfo, Operator): """Opens up the File Explorer to the designated folder location""" bl_idname = "grab_doc.open_folder" bl_label = "Open Folder" - def execute(self, context: types.Context): + def execute(self, context: Context): try: - bpy.ops.wm.path_open(filepath = bpy.path.abspath(context.scene.grabDoc.exportPath)) + bpy.ops.wm.path_open( + filepath=bpy.path.abspath(context.scene.grabDoc.exportPath) + ) except RuntimeError: - self.report({'ERROR'}, ErrorCodeConst.NO_VALID_PATH_SET) + self.report({'ERROR'}, Error.NO_VALID_PATH_SET) return {'CANCELLED'} return {'FINISHED'} -class GRABDOC_OT_view_cam(OpInfo, types.Operator): +class GRABDOC_OT_view_cam(OpInfo, Operator): """View the GrabDoc camera""" bl_idname = "grab_doc.view_cam" from_modal: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) - def execute(self, context: types.Context): - context.scene.camera = bpy.data.objects[GlobalVarConst.TRIM_CAMERA_NAME] + def execute(self, context: Context): + context.scene.camera = bpy.data.objects[Global.TRIM_CAMERA_NAME] - # TODO I don't know what the intention was here + # TODO: I don't know what the intention was here if self.from_modal and is_camera_in_3d_view(): bpy.ops.view3d.view_camera() else: @@ -75,67 +103,78 @@ def execute(self, context: types.Context): return {'FINISHED'} -################################################################################################################ +################################################ # SCENE SETUP & CLEANUP -################################################################################################################ +################################################ -class GRABDOC_OT_setup_scene(OpInfo, types.Operator): - """Setup / Refresh your current scene. Useful if you messed up something within the GrabDoc collections that you don't know how to properly revert""" +class GRABDOC_OT_setup_scene(OpInfo, Operator): + """Setup / Refresh your current scene. Useful if you messed +up something within the GrabDoc collections that you don't +know how to properly revert""" bl_idname = "grab_doc.setup_scene" bl_label = "Setup / Refresh GrabDoc Scene" - def execute(self, context: types.Context): + def execute(self, context: Context): scene_setup(self, context) return {'FINISHED'} -class GRABDOC_OT_remove_setup(OpInfo, types.Operator): - """Completely removes every element of GrabDoc from the scene, not including images reimported after bakes""" +class GRABDOC_OT_remove_setup(OpInfo, Operator): + """Completely removes every element of GrabDoc from the +scene, not including images reimported after bakes""" bl_idname = "grab_doc.remove_setup" bl_label = "Remove Setup" - def execute(self, context: types.Context): + def execute(self, context: Context): remove_setup(context) return {'FINISHED'} -################################################################################################################ +################################################ # MAP EXPORTER -################################################################################################################ +################################################ -class GRABDOC_OT_export_maps(OpInfo, types.Operator): +class GRABDOC_OT_export_maps(OpInfo, Operator, UILayout): """Export all enabled bake maps""" bl_idname = "grab_doc.export_maps" bl_label = "Export Maps" bl_options = {'INTERNAL'} + progress_factor = 0.0 + @classmethod - def poll(cls, context: types.Context) -> bool: + def poll(cls, context: Context) -> bool: return not context.scene.grabDoc.modalState - def grabdoc_export(self, context: types.Context, export_suffix: str) -> None: - grabDoc = context.scene.grabDoc + def grabdoc_export(self, context: Context, export_suffix: str) -> None: + gd = context.scene.grabDoc render = context.scene.render # Save - file output path saved_path = render.filepath - # Set - Output path to add-on path + add-on name + the type of map exported (file extensions handled automatically) - render.filepath = bpy.path.abspath(grabDoc.exportPath) + grabDoc.exportName + '_' + export_suffix + # Set - Output path to add-on path + add-on name + the + # type of map exported (file extensions handled automatically) + render.filepath = \ + bpy.path.abspath(gd.exportPath) + gd.exportName + '_' + export_suffix - context.scene.camera = bpy.data.objects[GlobalVarConst.TRIM_CAMERA_NAME] + context.scene.camera = bpy.data.objects[Global.TRIM_CAMERA_NAME] bpy.ops.render.render(write_still=True) # Refresh - file output path render.filepath = saved_path - def execute(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + layout = self.layout + print("Layout:", layout) - print(self.map_types) + def execute(self, context: Context): + gd = context.scene.grabDoc + + #print(self.map_types) self.map_types = 'export' print(self.map_types) @@ -150,15 +189,15 @@ def execute(self, context: types.Context): # System for dynamically deciding the progress percentage operation_counter = ( - (grabDoc.uiVisibilityNormals and grabDoc.exportNormals), - (grabDoc.uiVisibilityCurvature and grabDoc.exportCurvature), - (grabDoc.uiVisibilityOcclusion and grabDoc.exportOcclusion), - (grabDoc.uiVisibilityHeight and grabDoc.exportHeight), - (grabDoc.uiVisibilityAlpha and grabDoc.exportAlpha), - (grabDoc.uiVisibilityMatID and grabDoc.exportMatID), - (grabDoc.uiVisibilityAlbedo and grabDoc.exportAlbedo), - (grabDoc.uiVisibilityRoughness and grabDoc.exportRoughness), - (grabDoc.uiVisibilityMetalness and grabDoc.exportMetalness) + (gd.uiVisibilityNormals and gd.exportNormals), + (gd.uiVisibilityCurvature and gd.exportCurvature), + (gd.uiVisibilityOcclusion and gd.exportOcclusion), + (gd.uiVisibilityHeight and gd.exportHeight), + (gd.uiVisibilityAlpha and gd.exportAlpha), + (gd.uiVisibilityMatID and gd.exportMatID), + (gd.uiVisibilityAlbedo and gd.exportAlbedo), + (gd.uiVisibilityRoughness and gd.exportRoughness), + (gd.uiVisibilityMetalness and gd.exportMetalness) ) percentage_division = 100 / (1 + sum(operation_counter)) @@ -172,93 +211,93 @@ def execute(self, context: types.Context): modeCallback = context.object.mode if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') active_selected = True # Scale up BG Plane (helps overscan & border pixels) - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 3 percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityNormals and grabDoc.exportNormals: + if gd.uiVisibilityNormals and gd.exportNormals: normals_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixNormals) + self.grabdoc_export(context, export_suffix=gd.suffixNormals) - if grabDoc.reimportAsMatNormals: - reimport_as_material(grabDoc.suffixNormals) + if gd.reimportAsMatNormals: + reimport_as_material(gd.suffixNormals) - cleanup_ng_from_mat(GlobalVarConst.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NG_NORMAL_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityCurvature and grabDoc.exportCurvature: + if gd.uiVisibilityCurvature and gd.exportCurvature: curvature_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixCurvature) + self.grabdoc_export(context, export_suffix=gd.suffixCurvature) curvature_refresh(self, context) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityOcclusion and grabDoc.exportOcclusion: + if gd.uiVisibilityOcclusion and gd.exportOcclusion: occlusion_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixOcclusion) + self.grabdoc_export(context, export_suffix=gd.suffixOcclusion) - if grabDoc.reimportAsMatOcclusion: - reimport_as_material(grabDoc.suffixOcclusion) + if gd.reimportAsMatOcclusion: + reimport_as_material(gd.suffixOcclusion) - cleanup_ng_from_mat(GlobalVarConst.NG_AO_NAME) + cleanup_ng_from_mat(Global.NG_AO_NAME) occlusion_refresh(self, context) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityHeight and grabDoc.exportHeight: + if gd.uiVisibilityHeight and gd.exportHeight: height_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixHeight) - cleanup_ng_from_mat(GlobalVarConst.NG_HEIGHT_NAME) + self.grabdoc_export(context, export_suffix=gd.suffixHeight) + cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityAlpha and grabDoc.exportAlpha: + if gd.uiVisibilityAlpha and gd.exportAlpha: alpha_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixAlpha) - cleanup_ng_from_mat(GlobalVarConst.NG_ALPHA_NAME) + self.grabdoc_export(context, export_suffix=gd.suffixAlpha) + cleanup_ng_from_mat(Global.NG_ALPHA_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityMatID and grabDoc.exportMatID: + if gd.uiVisibilityMatID and gd.exportMatID: id_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixID) + self.grabdoc_export(context, export_suffix=gd.suffixID) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityAlbedo and grabDoc.exportAlbedo: + if gd.uiVisibilityAlbedo and gd.exportAlbedo: albedo_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixAlbedo) - cleanup_ng_from_mat(GlobalVarConst.NG_ALBEDO_NAME) + self.grabdoc_export(context, export_suffix=gd.suffixAlbedo) + cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityRoughness and grabDoc.exportRoughness: + if gd.uiVisibilityRoughness and gd.exportRoughness: roughness_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixRoughness) - cleanup_ng_from_mat(GlobalVarConst.NG_ROUGHNESS_NAME) + self.grabdoc_export(context, export_suffix=gd.suffixRoughness) + cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if grabDoc.uiVisibilityMetalness and grabDoc.exportMetalness: + if gd.uiVisibilityMetalness and gd.exportMetalness: metalness_setup(self, context) - self.grabdoc_export(context, export_suffix=grabDoc.suffixMetalness) - cleanup_ng_from_mat(GlobalVarConst.NG_METALNESS_NAME) + self.grabdoc_export(context, export_suffix=gd.suffixMetalness) + cleanup_ng_from_mat(Global.NG_METALNESS_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) @@ -266,35 +305,38 @@ def execute(self, context: types.Context): # Refresh all original settings export_refresh(self, context) - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 1 - if grabDoc.exportPlane: + if gd.exportPlane: export_bg_plane(context) - if grabDoc.openFolderOnExport: - bpy.ops.wm.path_open(filepath = bpy.path.abspath(grabDoc.exportPath)) + if gd.openFolderOnExport: + bpy.ops.wm.path_open(filepath=bpy.path.abspath(gd.exportPath)) - # Call for Original Context Mode (Use bpy.ops so that Blenders viewport refreshes) + # Call for Original Context Mode, use bpy.ops + # so that Blenders viewport refreshes if active_selected: context.view_layer.objects.active = bpy.data.objects[activeCallback] if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = modeCallback) + bpy.ops.object.mode_set(mode=modeCallback) # End the timer & UI progress bar end = time.time() - execution_time = round(end - start, 2) + exc_time = round(end - start, 2) - self.report({'INFO'}, f"{ErrorCodeConst.EXPORT_COMPLETE} (execution time: {execution_time}s)") + self.report( + {'INFO'}, f"{Error.EXPORT_COMPLETE} (execution time: {exc_time}s)" + ) context.window_manager.progress_end() return {'FINISHED'} -################################################################################################################ +################################################ # OFFLINE RENDERER -################################################################################################################ +################################################ class MapEnum(): @@ -314,18 +356,22 @@ class MapEnum(): ) -class GRABDOC_OT_offline_render(OpInfo, MapEnum, types.Operator): +class GRABDOC_OT_offline_render(OpInfo, MapEnum, Operator): """Renders the selected material and previews it inside Blender""" bl_idname = "grab_doc.offline_render" bl_options = {'INTERNAL'} - # TODO - might be able to have this uniquely utilize compositing for mixing maps? - # - support Reimport as Materials - # - Support correct default colorspaces + # TODO: + # - Might be able to have this uniquely + # utilize compositing for mixing maps? + # - Support Reimport as Materials + # - Support correct default colorspaces @classmethod - def poll(cls, context: types.Context) -> bool: - return True if not context.scene.grabDoc.modalState else poll_message_error(cls, "Cannot render, in a Modal State") + def poll(cls, context: Context) -> bool: + return True if not context.scene.grabDoc.modalState else poll_message_error( + cls, "Cannot render, in a Modal State" + ) def offline_render(self): render = bpy.context.scene.render @@ -360,8 +406,9 @@ def offline_render(self): area.type = "IMAGE_EDITOR" area.spaces.active.image = new_image - def execute(self, context: types.Context): - report_value, report_string = bad_setup_check(self, context, active_export=False) + def execute(self, context: Context): + report_value, report_string = \ + bad_setup_check(self, context, active_export=False) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} @@ -377,19 +424,20 @@ def execute(self, context: types.Context): modeCallback = context.object.mode if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') active_selected = True # Scale up BG Plane (helps overscan & border pixels) - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 3 - match self.map_types: # NOTE lol at remembering match case exists, looks slightly better vs conditional in this case + # NOTE: Match case exists, looks slightly better vs conditional in this case + match self.map_types: case "normals": normals_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NG_NORMAL_NAME) case "curvature": curvature_setup(self, context) self.offline_render() @@ -397,67 +445,70 @@ def execute(self, context: types.Context): case "occlusion": occlusion_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_AO_NAME) + cleanup_ng_from_mat(Global.NG_AO_NAME) occlusion_refresh(self, context) case "height": height_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_HEIGHT_NAME) + cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) case "ID": id_setup(self, context) self.offline_render() case "alpha": alpha_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_ALPHA_NAME) + cleanup_ng_from_mat(Global.NG_ALPHA_NAME) case "albedo": albedo_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_ALBEDO_NAME) + cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) case "roughness": roughness_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_ROUGHNESS_NAME) + cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) case "metalness": metalness_setup(self, context) self.offline_render() - cleanup_ng_from_mat(GlobalVarConst.NG_METALNESS_NAME) + cleanup_ng_from_mat(Global.NG_METALNESS_NAME) # Refresh all original settings export_refresh(self, context) # Scale down BG Plane (helps overscan & border pixels) - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 1 - # Call for Original Context Mode (Use bpy.ops so that Blenders viewport refreshes) + # Call for Original Context Mode, use bpy.ops so + # that Blenders viewport refreshes if active_selected: context.view_layer.objects.active = bpy.data.objects[activeCallback] if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = modeCallback) + bpy.ops.object.mode_set(mode=modeCallback) # End the timer end = time.time() - execution_time = round(end - start, 2) + exc_time = round(end - start, 2) - self.report({'INFO'}, f"{ErrorCodeConst.OFFLINE_RENDER_COMPLETE} (execution time: {execution_time}s)") + self.report( + {'INFO'}, f"{Error.OFFLINE_RENDER_COMPLETE} (execution time: {exc_time}s)" + ) return {'FINISHED'} -################################################################################################################ +################################################ # MAP PREVIEWER -################################################################################################################ +################################################ -class GRABDOC_OT_leave_map_preview(types.Operator): +class GRABDOC_OT_leave_map_preview(Operator): """Exit the current Map Preview""" bl_idname = "grab_doc.leave_modal" bl_label = "Exit Map Preview" bl_options = {'INTERNAL', 'REGISTER'} - def execute(self, context: types.Context): - # NOTE A lot of the modal system relies on this particular + def execute(self, context: Context): + # NOTE: A lot of the modal system relies on this particular # switch, if it notices this has been disabled it will begin # the exiting sequence in the properties update function and # also in the modal method itself @@ -465,29 +516,30 @@ def execute(self, context: types.Context): return {'FINISHED'} -class GRABDOC_OT_map_preview_warning(OpInfo, MapEnum, types.Operator): +class GRABDOC_OT_map_preview_warning(OpInfo, MapEnum, Operator): """Preview the selected material""" bl_idname = "grab_doc.preview_warning" bl_label = "MATERIAL PREVIEW WARNING" bl_options = {'INTERNAL'} - def invoke(self, context: types.Context, _event: types.Event): + def invoke(self, context: Context, _event: Event): return context.window_manager.invoke_props_dialog(self, width=525) - def draw(self, _context: types.Context): + def draw(self, _context: Context): col = self.layout.column(align=True) - for line in GlobalVarConst.PREVIEW_WARNING.split('\n')[1:][:-1]: + for line in Global.PREVIEW_WARNING.split('\n')[1:][:-1]: col.label(text=line) - def execute(self, context: types.Context): + def execute(self, context: Context): context.scene.grabDoc.firstBakePreview = False bpy.ops.grab_doc.preview_map(map_types=self.map_types) return {'FINISHED'} -def draw_callback_px(self, context: types.Context) -> None: - """This needs to be outside of the class because of how draw_handler_add handles args""" +def draw_callback_px(self, context: Context) -> None: + """This needs to be outside of the class + because of how draw_handler_add handles args""" render_text = self.map_types.capitalize() if self.map_types == 'ID': # Exception for Material ID preview render_text = "Material ID" @@ -524,22 +576,22 @@ def draw_callback_px(self, context: types.Context) -> None: blf.color(font_id, *(1, 1, 0, font_opacity)) blf.draw(font_id, "You are in Map Preview mode!") -class GRABDOC_OT_map_preview(OpInfo, MapEnum, types.Operator): +class GRABDOC_OT_map_preview(OpInfo, MapEnum, Operator): """Preview the selected material""" bl_idname = "grab_doc.preview_map" bl_options = {'INTERNAL'} - def modal(self, context: types.Context, event: types.Event): + def modal(self, context: Context, event: Event): scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc - scene.camera = bpy.data.objects[GlobalVarConst.TRIM_CAMERA_NAME] + scene.camera = bpy.data.objects[Global.TRIM_CAMERA_NAME] # Set - Exporter settings image_settings = scene.render.image_settings # If background plane not visible in render, enable alpha channel - if not grabDoc.collRendered: + if not gd.collRendered: scene.render.film_transparent = True image_settings.color_mode = 'RGBA' @@ -549,64 +601,64 @@ def modal(self, context: types.Context, event: types.Event): image_settings.color_mode = 'RGB' # Get correct file format and color depth - image_settings.file_format = grabDoc.imageType + image_settings.file_format = gd.imageType - if grabDoc.imageType == 'OPEN_EXR': - image_settings.color_depth = grabDoc.colorDepthEXR - elif grabDoc.imageType != 'TARGA': - image_settings.color_depth = grabDoc.colorDepth + if gd.imageType == 'OPEN_EXR': + image_settings.color_depth = gd.colorDepthEXR + elif gd.imageType != 'TARGA': + image_settings.color_depth = gd.colorDepth # Bake map specific settings that are forcibly kept in check - # TODO update the node group input values for Mixed Normal + # TODO: update the node group input values for Mixed Normal view_settings = scene.view_settings if self.map_types == "curvature": - view_settings.look = grabDoc.contrastCurvature.replace('_', ' ') + view_settings.look = gd.contrastCurvature.replace('_', ' ') - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].color[3] = .9999 + bpy.data.objects[Global.BG_PLANE_NAME].color[3] = .9999 elif self.map_types == "occlusion": - view_settings.look = grabDoc.contrastOcclusion.replace('_', ' ') + view_settings.look = gd.contrastOcclusion.replace('_', ' ') - #ao_node = bpy.data.node_groups[NG_AO_NAME].nodes.get('Ambient Occlusion') - #ao_node.inputs[1].default_value = grabDoc.distanceOcclusion + #ao = bpy.data.node_groups[NG_AO_NAME].nodes.get('Ambient Occlusion') + #ao.inputs[1].default_value = gd.distanceOcclusion elif self.map_types == "height": - view_settings.look = grabDoc.contrastHeight.replace('_', ' ') + view_settings.look = gd.contrastHeight.replace('_', ' ') elif self.map_types == "ID": - scene.display.shading.color_type = grabDoc.methodMatID + scene.display.shading.color_type = gd.methodMatID # Exit check - if not grabDoc.modalState or event.type in {'ESC'} or not proper_scene_setup(): + if not gd.modalState or event.type in {'ESC'} or not proper_scene_setup(): self.modal_cleanup(context) return {'CANCELLED'} return {'PASS_THROUGH'} - def modal_cleanup(self, context: types.Context) -> None: - grabDoc = context.scene.grabDoc + def modal_cleanup(self, context: Context) -> None: + gd = context.scene.grabDoc - grabDoc.modalState = False + gd.modalState = False - bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') if self.map_types == "normals": - cleanup_ng_from_mat(GlobalVarConst.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NG_NORMAL_NAME) elif self.map_types == "curvature": curvature_refresh(self, context) elif self.map_types == "occlusion": occlusion_refresh(self, context) - cleanup_ng_from_mat(GlobalVarConst.NG_AO_NAME) + cleanup_ng_from_mat(Global.NG_AO_NAME) elif self.map_types == "height": - cleanup_ng_from_mat(GlobalVarConst.NG_HEIGHT_NAME) + cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) elif self.map_types == "alpha": - cleanup_ng_from_mat(GlobalVarConst.NG_ALPHA_NAME) + cleanup_ng_from_mat(Global.NG_ALPHA_NAME) elif self.map_types == "albedo": - cleanup_ng_from_mat(GlobalVarConst.NG_ALBEDO_NAME) + cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) elif self.map_types == "roughness": - cleanup_ng_from_mat(GlobalVarConst.NG_ROUGHNESS_NAME) + cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) elif self.map_types == "metalness": - cleanup_ng_from_mat(GlobalVarConst.NG_METALNESS_NAME) + cleanup_ng_from_mat(Global.NG_METALNESS_NAME) export_refresh(self, context) @@ -625,24 +677,26 @@ def modal_cleanup(self, context: types.Context) -> None: space.shading.type = self.saved_render_view break - grabDoc.bakerType = self.savedBakerType + gd.bakerType = self.savedBakerType - # Check for auto exit camera option (Keep this at the end of the stack to avoid pop in) - if grabDoc.autoExitCamera or not proper_scene_setup(): + # Check for auto exit camera option, keep this + # at the end of the stack to avoid pop in + if gd.autoExitCamera or not proper_scene_setup(): bpy.ops.grab_doc.view_cam(from_modal=True) - def execute(self, context: types.Context): - grabDoc = context.scene.grabDoc + def execute(self, context: Context): + gd = context.scene.grabDoc - report_value, report_string = bad_setup_check(self, context, active_export=False) + report_value, report_string = \ + bad_setup_check(self, context, active_export=False) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} - grabDoc.modalState = True + gd.modalState = True if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') export_and_preview_setup(self, context) @@ -660,11 +714,11 @@ def execute(self, context: types.Context): break # Save & Set - UI Baker Type - self.savedBakerType = grabDoc.bakerType - grabDoc.bakerType = 'Blender' + self.savedBakerType = gd.bakerType + gd.bakerType = 'Blender' # Set - Preview type - grabDoc.modalPreviewType = self.map_types + gd.modalPreviewType = self.map_types if self.map_types == 'normals': normals_setup(self, context) @@ -686,24 +740,26 @@ def execute(self, context: types.Context): id_setup(self, context) # Draw text handler - self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + self._handle = SpaceView3D.draw_handler_add( + draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL' + ) # Modal handler context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} -class GRABDOC_OT_export_current_preview(OpInfo, types.Operator): +class GRABDOC_OT_export_current_preview(OpInfo, Operator): """Export the currently previewed material""" bl_idname = "grab_doc.export_preview" bl_label = "Export Previewed Map" @classmethod - def poll(cls, context: types.Context) -> bool: + def poll(cls, context: Context) -> bool: return context.scene.grabDoc.modalState - def execute(self, context: types.Context): - grabDoc = context.scene.grabDoc + def execute(self, context: Context): + gd = context.scene.grabDoc report_value, report_string = bad_setup_check(self, context, active_export=True) if report_value: @@ -717,30 +773,32 @@ def execute(self, context: types.Context): render = context.scene.render saved_path = render.filepath - if grabDoc.modalPreviewType == 'normals': - export_suffix = grabDoc.suffixNormals - elif grabDoc.modalPreviewType == 'curvature': - export_suffix = grabDoc.suffixCurvature - elif grabDoc.modalPreviewType == 'occlusion': - export_suffix = grabDoc.suffixOcclusion - elif grabDoc.modalPreviewType == 'height': - export_suffix = grabDoc.suffixHeight - elif grabDoc.modalPreviewType == 'ID': - export_suffix = grabDoc.suffixID - elif grabDoc.modalPreviewType == 'alpha': - export_suffix = grabDoc.suffixAlpha - elif grabDoc.modalPreviewType == 'albedo': - export_suffix = grabDoc.suffixAlbedo - elif grabDoc.modalPreviewType == 'roughness': - export_suffix = grabDoc.suffixRoughness - elif grabDoc.modalPreviewType == 'metalness': - export_suffix = grabDoc.suffixMetalness - - # Set - Output path to add-on path + add-on name + the type of map exported (file extensions get handled automatically) - render.filepath = bpy.path.abspath(grabDoc.exportPath) + grabDoc.exportName + f"_{export_suffix}" + if gd.modalPreviewType == 'normals': + export_suffix = gd.suffixNormals + elif gd.modalPreviewType == 'curvature': + export_suffix = gd.suffixCurvature + elif gd.modalPreviewType == 'occlusion': + export_suffix = gd.suffixOcclusion + elif gd.modalPreviewType == 'height': + export_suffix = gd.suffixHeight + elif gd.modalPreviewType == 'ID': + export_suffix = gd.suffixID + elif gd.modalPreviewType == 'alpha': + export_suffix = gd.suffixAlpha + elif gd.modalPreviewType == 'albedo': + export_suffix = gd.suffixAlbedo + elif gd.modalPreviewType == 'roughness': + export_suffix = gd.suffixRoughness + elif gd.modalPreviewType == 'metalness': + export_suffix = gd.suffixMetalness + + # Set - Output path to add-on path + add-on name + the + # type of map exported (file extensions get handled automatically) + render.filepath = \ + bpy.path.abspath(gd.exportPath) + gd.exportName + f"_{export_suffix}" # Set - Scale up the plane - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 3 bpy.ops.render.render(write_still=True) @@ -749,51 +807,53 @@ def execute(self, context: types.Context): render.filepath = saved_path # Reimport the Normal/Occlusion map as a material if requested - if grabDoc.modalPreviewType == 'normals' and grabDoc.reimportAsMatNormals: - reimport_as_material(grabDoc.suffixNormals) - elif grabDoc.modalPreviewType == 'occlusion' and grabDoc.reimportAsMatOcclusion: - reimport_as_material(grabDoc.suffixOcclusion) + if gd.modalPreviewType == 'normals' and gd.reimportAsMatNormals: + reimport_as_material(gd.suffixNormals) + elif gd.modalPreviewType == 'occlusion' and gd.reimportAsMatOcclusion: + reimport_as_material(gd.suffixOcclusion) # Refresh - Scale down the plane - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 1 - if grabDoc.exportPlane: + if gd.exportPlane: export_bg_plane(context) # Open export path location if requested - if grabDoc.openFolderOnExport: - bpy.ops.wm.path_open(filepath = bpy.path.abspath(grabDoc.exportPath)) + if gd.openFolderOnExport: + bpy.ops.wm.path_open(filepath=bpy.path.abspath(gd.exportPath)) # End the timer end = time.time() - execution_time = round(end - start, 2) + exc_time = round(end - start, 2) - self.report({'INFO'}, f"{ErrorCodeConst.EXPORT_COMPLETE} (execution time: {execution_time}s)") + self.report( + {'INFO'}, f"{Error.EXPORT_COMPLETE} (execution time: {exc_time}s)" + ) return {'FINISHED'} -class GRABDOC_OT_map_pack_info(OpInfo, types.Operator): - """Information about Map Packing and current limitations""" - bl_idname = "grab_doc.map_pack_info" - bl_label = "MAP PACKING INFORMATION" - bl_options = {'INTERNAL'} +#class GRABDOC_OT_map_pack_info(OpInfo, Operator): +# """Information about Map Packing and current limitations""" +# bl_idname = "grab_doc.map_pack_info" +# bl_label = "MAP PACKING INFORMATION" +# bl_options = {'INTERNAL'}# - def invoke(self, context: types.Context, _event: types.Event): - return context.window_manager.invoke_props_dialog(self, width=500) +# def invoke(self, context: Context, _event: Event): +# return context.window_manager.invoke_props_dialog(self, width=500) - def draw(self, _context: types.Context): - col = self.layout.column(align=True) - for line in GlobalVarConst.PACK_MAPS_WARNING.split('\n')[1:][:-1]: - col.label(text=line) - - def execute(self, context: types.Context): - return {'FINISHED'} +# def draw(self, _context: Context): +# col = self.layout.column(align=True) +# for line in Global.PACK_MAPS_WARNING.split('\n')[1:][:-1]: +# col.label(text=line) +# +# def execute(self, context: Context): +# return {'FINISHED'} -################################################################################################################ +################################################ # REGISTRATION -################################################################################################################ +################################################ classes = ( @@ -807,8 +867,8 @@ def execute(self, context: types.Context): GRABDOC_OT_map_preview_warning, GRABDOC_OT_map_preview, GRABDOC_OT_leave_map_preview, - GRABDOC_OT_export_current_preview, - GRABDOC_OT_map_pack_info + GRABDOC_OT_export_current_preview + #GRABDOC_OT_map_pack_info ) diff --git a/preferences.py b/preferences.py index 00fa466..8e07b11 100644 --- a/preferences.py +++ b/preferences.py @@ -1,36 +1,57 @@ -import bpy, os, bpy.types as types -from bpy.props import BoolProperty, PointerProperty, StringProperty, EnumProperty, IntProperty, FloatProperty +import os + +import bpy from bl_operators.presets import AddPresetBase from bl_ui.utils import PresetPanel -from .operators import find_tallest_object -from .scene_setup_utils import scene_setup -from .render_setup_utils import get_rendered_objects +from bpy.types import ( + Menu, + Panel, + Operator, + AddonPreferences, + Context, + PropertyGroup, + Image, + Scene, + Collection, + Object +) +from bpy.props import ( + BoolProperty, + PointerProperty, + StringProperty, + EnumProperty, + IntProperty, + FloatProperty +) + +from .constants import GlobalVariableConstants as Global +from .utils.scene import scene_setup +from .utils.render import get_rendered_objects, set_guide_height + from .addon_updater import Updater as updater -from .__init__ import bl_info -from .constants import GlobalVariableConstants as GlobalVarConst -from .generic_utils import OpInfo +from .constants import VERSION -################################################################################################################ +################################################ # PRESETS -################################################################################################################ +################################################ -class GRABDOC_MT_presets(types.Menu): +class GRABDOC_MT_presets(Menu): bl_label = "" preset_subdir = "grabDoc" preset_operator = "script.execute_preset" - draw = types.Menu.draw_preset + draw = Menu.draw_preset -class GRABDOC_PT_presets(PresetPanel, types.Panel): +class GRABDOC_PT_presets(PresetPanel, Panel): bl_label = 'GrabDoc Presets' preset_subdir = 'grab_doc' preset_operator = 'script.execute_preset' preset_add_operator = 'grab_doc.preset_add' -class GRABDOC_OT_add_preset(AddPresetBase, types.Operator): +class GRABDOC_OT_add_preset(AddPresetBase, Operator): bl_idname = "grab_doc.preset_add" bl_label = "Add a new preset" preset_menu = "GRABDOC_MT_presets" @@ -38,109 +59,112 @@ class GRABDOC_OT_add_preset(AddPresetBase, types.Operator): # Variable used for all preset values preset_defines = ["grabDoc=bpy.context.scene.grabDoc"] + # TODO: Create a function that + # generates this list below + # Properties to store in the preset preset_values = [ - "grabDoc.collSelectable", - "grabDoc.collVisible", - "grabDoc.collRendered", - "grabDoc.useGrid", - "grabDoc.gridSubdivisions", - "grabDoc.useFiltering", - "grabDoc.widthFiltering", - "grabDoc.scalingSet", - - "grabDoc.bakerType", - "grabDoc.exportPath", - "grabDoc.exportName", - "grabDoc.exportResX", - "grabDoc.exportResY", - "grabDoc.lockRes", - "grabDoc.imageType", - "grabDoc.colorDepth", - "grabDoc.colorDepthTGA", - "grabDoc.imageCompPNG", - - "grabDoc.onlyRenderColl", - "grabDoc.exportPlane", - "grabDoc.openFolderOnExport", - "grabDoc.autoExitCamera", - - "grabDoc.uiVisibilityNormals", - "grabDoc.uiVisibilityCurvature", - "grabDoc.uiVisibilityOcclusion", - "grabDoc.uiVisibilityHeight", - "grabDoc.uiVisibilityMatID", - "grabDoc.uiVisibilityAlpha", - "grabDoc.uiVisibilityAlbedo", - "grabDoc.uiVisibilityRoughness", - "grabDoc.uiVisibilityMetalness", - - "grabDoc.exportNormals", - "grabDoc.reimportAsMatNormals", - "grabDoc.flipYNormals", - "grabDoc.useTextureNormals", - "grabDoc.samplesNormals", - "grabDoc.suffixNormals", - "grabDoc.samplesCyclesNormals", - "grabDoc.engineNormals", - - "grabDoc.exportCurvature", - "grabDoc.ridgeCurvature", - "grabDoc.valleyCurvature", - "grabDoc.samplesCurvature", - "grabDoc.contrastCurvature", - "grabDoc.suffixCurvature", - - "grabDoc.exportOcclusion", - "grabDoc.reimportAsMatOcclusion", - "grabDoc.gammaOcclusion", - "grabDoc.distanceOcclusion", - "grabDoc.samplesOcclusion", - "grabDoc.contrastOcclusion", - "grabDoc.suffixOcclusion", - - "grabDoc.exportHeight", - "grabDoc.rangeTypeHeight", - "grabDoc.guideHeight", - "grabDoc.invertMaskHeight", - "grabDoc.samplesHeight", - "grabDoc.contrastHeight", - "grabDoc.suffixHeight", - - "grabDoc.exportAlpha", - "grabDoc.invertMaskAlpha", - "grabDoc.samplesAlpha", - "grabDoc.suffixAlpha", - - "grabDoc.exportMatID", - "grabDoc.methodMatID", - "grabDoc.samplesMatID", - "grabDoc.suffixID", - - "grabDoc.exportAlbedo", - "grabDoc.samplesAlbedo", - "grabDoc.suffixAlbedo", - "grabDoc.samplesCyclesAlbedo", - "grabDoc.engineAlbedo", - - "grabDoc.exportRoughness", - "grabDoc.invertMaskRoughness", - "grabDoc.samplesRoughness", - "grabDoc.suffixRoughness", - "grabDoc.samplesCyclesRoughness", - "grabDoc.engineRoughness", - - "grabDoc.exportMetalness", - "grabDoc.samplesMetalness", - "grabDoc.suffixMetalness", - "grabDoc.samplesCyclesMetalness", - "grabDoc.engineMetalness", - - "grabDoc.marmoAutoBake", - "grabDoc.marmoClosePostBake", - "grabDoc.marmoSamples", - "grabDoc.marmoAORayCount", - "grabDoc.imageType_marmo" + "gd.collSelectable", + "gd.collVisible", + "gd.collRendered", + "gd.useGrid", + "gd.gridSubdivisions", + "gd.useFiltering", + "gd.widthFiltering", + "gd.scalingSet", + + "gd.bakerType", + "gd.exportPath", + "gd.exportName", + "gd.exportResX", + "gd.exportResY", + "gd.lockRes", + "gd.imageType", + "gd.colorDepth", + "gd.colorDepthTGA", + "gd.imageCompPNG", + + "gd.useBakeCollection", + "gd.exportPlane", + "gd.openFolderOnExport", + "gd.autoExitCamera", + + "gd.uiVisibilityNormals", + "gd.uiVisibilityCurvature", + "gd.uiVisibilityOcclusion", + "gd.uiVisibilityHeight", + "gd.uiVisibilityMatID", + "gd.uiVisibilityAlpha", + "gd.uiVisibilityAlbedo", + "gd.uiVisibilityRoughness", + "gd.uiVisibilityMetalness", + + "gd.exportNormals", + "gd.reimportAsMatNormals", + "gd.flipYNormals", + "gd.useTextureNormals", + "gd.samplesNormals", + "gd.suffixNormals", + "gd.samplesCyclesNormals", + "gd.engineNormals", + + "gd.exportCurvature", + "gd.ridgeCurvature", + "gd.valleyCurvature", + "gd.samplesCurvature", + "gd.contrastCurvature", + "gd.suffixCurvature", + + "gd.exportOcclusion", + "gd.reimportAsMatOcclusion", + "gd.gammaOcclusion", + "gd.distanceOcclusion", + "gd.samplesOcclusion", + "gd.contrastOcclusion", + "gd.suffixOcclusion", + + "gd.exportHeight", + "gd.rangeTypeHeight", + "gd.guideHeight", + "gd.invertMaskHeight", + "gd.samplesHeight", + "gd.contrastHeight", + "gd.suffixHeight", + + "gd.exportAlpha", + "gd.invertMaskAlpha", + "gd.samplesAlpha", + "gd.suffixAlpha", + + "gd.exportMatID", + "gd.methodMatID", + "gd.samplesMatID", + "gd.suffixID", + + "gd.exportAlbedo", + "gd.samplesAlbedo", + "gd.suffixAlbedo", + "gd.samplesCyclesAlbedo", + "gd.engineAlbedo", + + "gd.exportRoughness", + "gd.invertMaskRoughness", + "gd.samplesRoughness", + "gd.suffixRoughness", + "gd.samplesCyclesRoughness", + "gd.engineRoughness", + + "gd.exportMetalness", + "gd.samplesMetalness", + "gd.suffixMetalness", + "gd.samplesCyclesMetalness", + "gd.engineMetalness", + + "gd.marmoAutoBake", + "gd.marmoClosePostBake", + "gd.marmoSamples", + "gd.marmoAORayCount", + "gd.imageType_marmo" ] # Where to store the preset @@ -152,17 +176,18 @@ class GRABDOC_OT_add_preset(AddPresetBase, types.Operator): ############################################################ -class GRABDOC_OT_check_for_update(OpInfo, types.Operator): +class GRABDOC_OT_check_for_update(Operator): bl_idname = "updater_gd.check_for_update" - bl_options = {'INTERNAL'} + bl_label = "" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} - def execute(self, _context: types.Context): + def execute(self, _context: Context): updater.check_for_update_now() return {'FINISHED'} -class GRABDOC_MT_addon_prefs(bpy.types.AddonPreferences): - bl_idname=__package__ +class GRABDOC_MT_addon_preferences(AddonPreferences): + bl_idname = __package__ # Store this here so the value is saved across project files marmoEXE: StringProperty( @@ -172,23 +197,26 @@ class GRABDOC_MT_addon_prefs(bpy.types.AddonPreferences): subtype="FILE_PATH" ) - def draw(self, _context: types.Context): - layout=self.layout - row=layout.row() + def draw(self, _context: Context): + layout = self.layout + row = layout.row() - if updater.update_ready == None: + if updater.update_ready is None: row.label(text="Checking for an update...") updater.check_for_update_now() elif updater.update_ready: - row.alert=True - row.label(text="There is a GrabDoc update available! Get it on Gumroad :D") + row.alert = True + row.label( + text="There is a GrabDoc update available! Get it on Gumroad :D") elif not updater.update_ready: - row.label(text="You have the latest version of GrabDoc! There are no new versions available.") - - row.operator("updater_gd.check_for_update", text="", icon="FILE_REFRESH") + row.label( + text="You have the latest version of GrabDoc! There are no new versions available." + ) + row.operator("updater_gd.check_for_update", + text="", icon="FILE_REFRESH") ############################################################ @@ -196,146 +224,178 @@ def draw(self, _context: types.Context): ############################################################ -class GRABDOC_property_group(bpy.types.PropertyGroup): +class GRABDOC_property_group(PropertyGroup): # UPDATE FUNCTIONS - def update_scaling_set(self, context: types.Context): + def update_scaling_set(self, context: Context): scene_setup(self, context) - gd_camera_ob_z = bpy.data.objects.get(GlobalVarConst.TRIM_CAMERA_NAME).location[2] + gd_camera_ob_z = bpy.data.objects.get( + Global.TRIM_CAMERA_NAME + ).location[2] + height_ng = bpy.data.node_groups.get(Global.NG_HEIGHT_NAME) - map_range_node = bpy.data.node_groups[GlobalVarConst.NG_HEIGHT_NAME].nodes.get('Map Range') - map_range_node.inputs[1].default_value = -self.guideHeight + gd_camera_ob_z - map_range_node.inputs[2].default_value = gd_camera_ob_z + map_range = height_ng.nodes.get('Map Range') + map_range.inputs[1].default_value = \ + - self.guideHeight + gd_camera_ob_z + map_range.inputs[2].default_value = gd_camera_ob_z - map_range_alpha_node = bpy.data.node_groups[GlobalVarConst.NG_ALPHA_NAME].nodes.get('Map Range') - map_range_alpha_node.inputs[1].default_value = gd_camera_ob_z - .00001 - map_range_alpha_node.inputs[2].default_value = gd_camera_ob_z + map_range_alpha = \ + bpy.data.node_groups[Global.NG_ALPHA_NAME].nodes.get('Map Range') + map_range_alpha.inputs[1].default_value = gd_camera_ob_z - .00001 + map_range_alpha.inputs[2].default_value = gd_camera_ob_z - def update_res_x(self, context: types.Context): - if self.lockRes: - if self.exportResX != self.exportResY: + def update_res_x(self, context: Context): + if self.lockRes and self.exportResX != self.exportResY: self.exportResY = self.exportResX - scene_setup(self, context) - def update_res_y(self, context: types.Context): - if self.lockRes: - if self.exportResY != self.exportResX: - self.exportResX = self.exportResY - + def update_res_y(self, context: Context): + if self.lockRes and self.exportResY != self.exportResX: + self.exportResX = self.exportResY scene_setup(self, context) - def update_export_name(self, _context: types.Context): + def update_export_name(self, _context: Context): if not self.exportName: self.exportName = "untitled" - def update_useTextureNormals(self, _context: types.Context): - if self.modalState: - ng_normal = bpy.data.node_groups[GlobalVarConst.NG_NORMAL_NAME] - vec_transform_node = ng_normal.nodes.get('Vector Transform') - group_output_node = ng_normal.nodes.get('Group Output') - - link = ng_normal.links - - if self.useTextureNormals: - link.new(vec_transform_node.inputs["Vector"], ng_normal.nodes.get('Bevel').outputs["Normal"]) - link.new(group_output_node.inputs["Output"], ng_normal.nodes.get('Mix Shader').outputs["Shader"]) - else: - link.new(vec_transform_node.inputs["Vector"], ng_normal.nodes.get('Bevel.001').outputs["Normal"]) - link.new(group_output_node.inputs["Output"], ng_normal.nodes.get('Vector Math.001').outputs["Vector"]) - - def update_curvature(self, context: types.Context): + def update_useTextureNormals(self, _context: Context) -> None: + if not self.modalState: + return + ng_normal = bpy.data.node_groups[Global.NG_NORMAL_NAME] + vec_transform = ng_normal.nodes.get('Vector Transform') + group_output = ng_normal.nodes.get('Group Output') + + links = ng_normal.links + if self.useTextureNormals: + links.new( + vec_transform.inputs["Vector"], + ng_normal.nodes.get('Bevel').outputs["Normal"] + ) + links.new( + group_output.inputs["Output"], + ng_normal.nodes.get('Mix Shader').outputs["Shader"] + ) + else: + links.new( + vec_transform.inputs["Vector"], + ng_normal.nodes.get('Bevel.001').outputs["Normal"] + ) + links.new( + group_output.inputs["Output"], + ng_normal.nodes.get('Vector Math.001').outputs["Vector"] + ) + + def update_curvature(self, context: Context): if self.modalState: - scene_shading = bpy.data.scenes[str(context.scene.name)].display.shading + scene_shading = bpy.data.scenes[str( + context.scene.name)].display.shading scene_shading.cavity_ridge_factor = scene_shading.curvature_ridge_factor = self.ridgeCurvature scene_shading.curvature_valley_factor = self.valleyCurvature - def update_flip_y(self, _context: types.Context): - vec_multiply_node = bpy.data.node_groups[GlobalVarConst.NG_NORMAL_NAME].nodes.get('Vector Math') - vec_multiply_node.inputs[1].default_value[1] = -.5 if self.flipYNormals else .5 + def update_flip_y(self, _context: Context): + vec_multiply = bpy.data.node_groups[Global.NG_NORMAL_NAME].nodes.get( + 'Vector Math') + vec_multiply.inputs[1].default_value[1] = -.5 if self.flipYNormals else .5 - def update_occlusion_gamma(self, _context: types.Context): - gamma_node = bpy.data.node_groups[GlobalVarConst.NG_AO_NAME].nodes.get('Gamma') - gamma_node.inputs[1].default_value = self.gammaOcclusion + def update_occlusion_gamma(self, _context: Context): + gamma = bpy.data.node_groups[Global.NG_AO_NAME].nodes.get('Gamma') + gamma.inputs[1].default_value = self.gammaOcclusion - def update_occlusion_distance(self, _context: types.Context): - ao_node = bpy.data.node_groups[GlobalVarConst.NG_AO_NAME].nodes.get('Ambient Occlusion') - ao_node.inputs[1].default_value = self.distanceOcclusion + def update_occlusion_distance(self, _context: Context): + ao = bpy.data.node_groups[Global.NG_AO_NAME].nodes.get( + 'Ambient Occlusion') + ao.inputs[1].default_value = self.distanceOcclusion - def update_manual_height_range(self, context: types.Context): + def update_manual_height_range(self, context: Context): scene_setup(self, context) - if self.modalState: + bpy.data.objects[Global.BG_PLANE_NAME].active_material = bpy.data.materials[Global.GD_MATERIAL_NAME] if self.rangeTypeHeight == 'AUTO': - self.rendered_obs = get_rendered_objects(context) - - find_tallest_object(self, context) - - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].active_material = bpy.data.materials[GlobalVarConst.GD_MATERIAL_NAME] - - def update_height_guide(self, context: types.Context): - gd_camera_ob_z = bpy.data.objects.get(GlobalVarConst.TRIM_CAMERA_NAME).location[2] - - map_range_node = bpy.data.node_groups[GlobalVarConst.NG_HEIGHT_NAME].nodes.get('Map Range') - map_range_node.inputs[1].default_value = gd_camera_ob_z + -self.guideHeight - map_range_node.inputs[2].default_value = gd_camera_ob_z - - ramp_node = bpy.data.node_groups[GlobalVarConst.NG_HEIGHT_NAME].nodes.get('ColorRamp') - ramp_node.color_ramp.elements[0].color = (0, 0, 0, 1) if self.invertMaskHeight else (1, 1, 1, 1) - ramp_node.color_ramp.elements[1].color = (1, 1, 1, 1) if self.invertMaskHeight else (0, 0, 0, 1) - ramp_node.location = (-400,0) + rendered_obs = get_rendered_objects(context) + set_guide_height(rendered_obs) + + def update_height_guide(self, context: Context): + gd_camera_ob_z = \ + bpy.data.objects.get(Global.TRIM_CAMERA_NAME).location[2] + + map_range = \ + bpy.data.node_groups[Global.NG_HEIGHT_NAME].nodes.get('Map Range') + map_range.inputs[1].default_value = \ + gd_camera_ob_z + -self.guideHeight + map_range.inputs[2].default_value = \ + gd_camera_ob_z + + ramp = bpy.data.node_groups[Global.NG_HEIGHT_NAME].nodes.get( + 'ColorRamp') + ramp.color_ramp.elements[0].color = \ + (0, 0, 0, 1) if self.invertMaskHeight else (1, 1, 1, 1) + ramp.color_ramp.elements[1].color = \ + (1, 1, 1, 1) if self.invertMaskHeight else (0, 0, 0, 1) + ramp.location = \ + (-400, 0) if self.rangeTypeHeight == 'MANUAL': scene_setup(self, context) # Update here so that it refreshes live in the VP if self.modalState: - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].active_material = bpy.data.materials[GlobalVarConst.GD_MATERIAL_NAME] + bpy.data.objects[Global.BG_PLANE_NAME].active_material = bpy.data.materials[Global.GD_MATERIAL_NAME] - def update_alpha(self, _context: types.Context): - gd_camera_ob_z = bpy.data.objects.get(GlobalVarConst.TRIM_CAMERA_NAME).location[2] + def update_alpha(self, _context: Context): + gd_camera_ob_z = bpy.data.objects.get( + Global.TRIM_CAMERA_NAME).location[2] - map_range_node = bpy.data.node_groups[GlobalVarConst.NG_ALPHA_NAME].nodes.get('Map Range') - map_range_node.inputs[1].default_value = gd_camera_ob_z - .00001 - map_range_node.inputs[2].default_value = gd_camera_ob_z + map_range = bpy.data.node_groups[Global.NG_ALPHA_NAME].nodes.get( + 'Map Range') + map_range.inputs[1].default_value = gd_camera_ob_z - .00001 + map_range.inputs[2].default_value = gd_camera_ob_z - invert_node = bpy.data.node_groups[GlobalVarConst.NG_ALPHA_NAME].nodes.get('Invert') - invert_node.inputs[0].default_value = 0 if self.invertMaskAlpha else 1 + invert = bpy.data.node_groups[Global.NG_ALPHA_NAME].nodes.get( + 'Invert') + invert.inputs[0].default_value = 0 if self.invertMaskAlpha else 1 # Update here so that it refreshes live in the VP if self.modalState: - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].active_material = bpy.data.materials[GlobalVarConst.GD_MATERIAL_NAME] + bpy.data.objects[Global.BG_PLANE_NAME].active_material = bpy.data.materials[Global.GD_MATERIAL_NAME] - def update_roughness(self, _context: types.Context): - invert_node = bpy.data.node_groups[GlobalVarConst.NG_ROUGHNESS_NAME].nodes.get('Invert') - invert_node.inputs[0].default_value = 1 if self.invertMaskRoughness else 0 + def update_roughness(self, _context: Context): + invert = bpy.data.node_groups[Global.NG_ROUGHNESS_NAME].nodes.get( + 'Invert') + invert.inputs[0].default_value = 1 if self.invertMaskRoughness else 0 # Update here so that it refreshes live in the VP - #if self.modalState: + # if self.modalState: # bpy.data.objects[BG_PLANE_NAME].active_material = bpy.data.materials[GD_MATERIAL_NAME] - def update_engine(self, context: types.Context): - if self.modalState: - if self.modalPreviewType == 'normals': - context.scene.render.engine = str(self.engineNormals).upper() - elif self.modalPreviewType == 'albedo': - context.scene.render.engine = str(self.engineAlbedo).upper() - elif self.modalPreviewType == 'roughness': - context.scene.render.engine = str(self.engineRoughness).upper() - elif self.modalPreviewType == 'metalness': - context.scene.render.engine = str(self.engineMetalness).upper() - - def update_export_path(self, _context: types.Context): - if self.exportPath != '' and not os.path.exists(bpy.path.abspath(self.exportPath)): + def update_engine(self, context: Context): + if not self.modalState: + return + if self.modalPreviewType == 'normals': + context.scene.render.engine = str(self.engineNormals).upper() + elif self.modalPreviewType == 'albedo': + context.scene.render.engine = str(self.engineAlbedo).upper() + elif self.modalPreviewType == 'roughness': + context.scene.render.engine = str(self.engineRoughness).upper() + elif self.modalPreviewType == 'metalness': + context.scene.render.engine = str(self.engineMetalness).upper() + + def update_export_path(self, _context: Context): + export_path_exists = \ + os.path.exists(bpy.path.abspath(self.exportPath)) + if self.exportPath != '' and not export_path_exists: self.exportPath = '' # PROPERTIES # Setup settings - collSelectable: BoolProperty(update=scene_setup, description='Sets the background plane selectibility') - collVisible: BoolProperty(default=True, update=scene_setup, description='Sets the visibility in the viewport') + collSelectable: BoolProperty( + update=scene_setup, + description='Sets the background plane selection capability' + ) + collVisible: BoolProperty(default=True, update=scene_setup, + description='Sets the visibility in the viewport') collRendered: BoolProperty( default=True, update=scene_setup, @@ -352,7 +412,7 @@ def update_export_path(self, _context: types.Context): description='Sets the scaling of the background plane and camera (WARNING: This will be the scaling used in tandem with `Export Plane as FBX`)', update=update_scaling_set ) - useFiltering: BoolProperty( # TODO add to cycles + useFiltering: BoolProperty( # TODO: add to cycles name='Use Filtering', default=True, description='Use pixel filtering on render. Useful to turn OFF for when you want to avoid aliased edges on stuff like Normal stamps', @@ -370,7 +430,7 @@ def update_export_path(self, _context: types.Context): refSelection: PointerProperty( name='Reference Selection', - type=bpy.types.Image, + type=Image, description='Select an image reference to use on the background plane', update=scene_setup ) @@ -406,10 +466,24 @@ def update_export_path(self, _context: types.Context): update=update_export_path ) - exportResX: IntProperty(name="Res X", default=2048, min=4, soft_max=8192, update=update_res_x) - exportResY: IntProperty(name="Res Y", default=2048, min=4, soft_max=8192, update=update_res_y) + exportResX: IntProperty( + name="Res X", + default=2048, + min=4, soft_max=8192, + update=update_res_x + ) + exportResY: IntProperty( + name="Res Y", + default=2048, + min=4, soft_max=8192, + update=update_res_y + ) - lockRes: BoolProperty(name='Sync Resolution', default=True, update=update_res_x) + lockRes: BoolProperty( + name='Sync Resolution', + default=True, + update=update_res_x + ) exportName: StringProperty( name="", @@ -436,7 +510,7 @@ def update_export_path(self, _context: types.Context): ) ) - imageCompPNG: IntProperty( # Use our own property so we can assign a new default + imageCompPNG: IntProperty( # Use our own property so we can assign a new default name="", default=50, min=0, @@ -462,14 +536,18 @@ def update_export_path(self, _context: types.Context): default='8' ) - onlyRenderColl: BoolProperty( + useBakeCollection: BoolProperty( description="This will add a collection to the scene which GrabDoc will ONLY render from, ignoring objects outside of it. This option is useful if objects aren't visible in the renders", update=scene_setup ) - exportPlane: BoolProperty(description="Exports the background plane as an FBX for use externally") + exportPlane: BoolProperty( + description="Exports the background plane as an FBX for use externally" + ) - openFolderOnExport: BoolProperty(description="Open the folder path in your File Explorer on map export") + openFolderOnExport: BoolProperty( + description="Open the folder path in your File Explorer on map export" + ) uiVisibilityNormals: BoolProperty(default=True) uiVisibilityCurvature: BoolProperty(default=True) @@ -481,10 +559,13 @@ def update_export_path(self, _context: types.Context): uiVisibilityRoughness: BoolProperty(default=True) uiVisibilityMetalness: BoolProperty(default=True) - # Map packing settings TODO ADD TO PRESETS - packMaps: BoolProperty(name='Enable Packing on Export', default=False) + # Map packing settings TODO: ADD TO PRESETS + packMaps: BoolProperty( + name='Enable Packing on Export', + default=False + ) - MAPTYPES = ( + MAP_TYPES = ( ('curvature', "Curvature", ""), ('occlusion', "Occlusion", ""), ('height', "Height", ""), @@ -493,33 +574,34 @@ def update_export_path(self, _context: types.Context): ('metalness', "Metalness", ""), ) - mapType_R: EnumProperty( - items=MAPTYPES, + channel_R: EnumProperty( + items=MAP_TYPES, default="occlusion", name='R' ) - mapType_G: EnumProperty( - items=MAPTYPES, + channel_G: EnumProperty( + items=MAP_TYPES, default="roughness", name='G' ) - mapType_B: EnumProperty( - items=MAPTYPES, + channel_B: EnumProperty( + items=MAP_TYPES, default="metalness", name='B' ) - mapType_A: EnumProperty( - items=MAPTYPES, + channel_A: EnumProperty( + items=MAP_TYPES, default="alpha", name='A' ) - # BAKE MAP SETTINGS # Normals exportNormals: BoolProperty(name='Export Normals', default=True) - reimportAsMatNormals: BoolProperty(description="Reimport the Normal map as a material for use in Blender") + reimportAsMatNormals: BoolProperty( + description="Reimport the Normal map as a material for use in Blender" + ) flipYNormals: BoolProperty( name="Flip Y (-Y)", description="Flip the normal map Y direction", @@ -605,7 +687,9 @@ def update_export_path(self, _context: types.Context): # Occlusion exportOcclusion: BoolProperty(name='Export Occlusion', default=True) - reimportAsMatOcclusion: BoolProperty(description="This will reimport the Occlusion map as a material for use in Blender") + reimportAsMatOcclusion: BoolProperty( + description="This will reimport the Occlusion map as a material for use in Blender" + ) gammaOcclusion: FloatProperty( default=1, min=.001, @@ -645,12 +729,21 @@ def update_export_path(self, _context: types.Context): ) # Height - exportHeight: BoolProperty(name='Export Height', default=True, update=scene_setup) + exportHeight: BoolProperty( + name='Export Height', default=True, update=scene_setup) invertMaskHeight: BoolProperty( description="Invert the Height mask, this is useful if you are sculpting into a plane mesh", update=update_height_guide ) - guideHeight: FloatProperty(name="", default=1, min=.01, soft_max=100, step=.03, subtype='DISTANCE', update=update_height_guide) + guideHeight: FloatProperty( + name="", + default=1, + min=.01, + soft_max=100, + step=.03, + subtype='DISTANCE', + update=update_height_guide + ) rangeTypeHeight: EnumProperty( items=( ('AUTO', "Auto", ""), @@ -680,7 +773,9 @@ def update_export_path(self, _context: types.Context): # Alpha exportAlpha: BoolProperty(name='Export Alpha', update=scene_setup) - invertMaskAlpha: BoolProperty(description="Invert the Alpha mask", update=update_alpha) + invertMaskAlpha: BoolProperty( + description="Invert the Alpha mask", update=update_alpha + ) suffixAlpha: StringProperty( name="", description="The suffix of the exported bake map", @@ -698,7 +793,7 @@ def update_export_path(self, _context: types.Context): ), name='ID Method' ) - fakeMethodMatID: EnumProperty( # Not actually used, just for UI representation + fakeMethodMatID: EnumProperty( # Not actually used, just for UI representation items=( ('RANDOM', "Random", ""), ('MATERIAL', "Material", ""), @@ -746,7 +841,9 @@ def update_export_path(self, _context: types.Context): # Roughness exportRoughness: BoolProperty(name='Export Roughness', update=scene_setup) - invertMaskRoughness: BoolProperty(description="Invert the Roughess (to make Glossines)", update=update_roughness) + invertMaskRoughness: BoolProperty( + description="Invert the Roughess (to make Glossines)", update=update_roughness + ) suffixRoughness: StringProperty( name="", description="The suffix of the exported bake map", @@ -786,7 +883,9 @@ def update_export_path(self, _context: types.Context): # MAP PREVIEW firstBakePreview: BoolProperty(default=True) - autoExitCamera: BoolProperty(description='Whether or not the camera view will automatically be left when exiting a Map Preview') + autoExitCamera: BoolProperty( + description='Whether or not the camera view will automatically be left when exiting a Map Preview' + ) modalState: BoolProperty() modalPreviewType: EnumProperty( items=( @@ -838,7 +937,7 @@ def update_export_path(self, _context: types.Context): GRABDOC_PT_presets, GRABDOC_OT_add_preset, GRABDOC_property_group, - GRABDOC_MT_addon_prefs, + GRABDOC_MT_addon_preferences, GRABDOC_OT_check_for_update ) @@ -847,14 +946,14 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.Scene.grabDoc = PointerProperty(type=GRABDOC_property_group) - bpy.types.Collection.is_gd_collection = BoolProperty() - bpy.types.Object.is_gd_object = BoolProperty() + Scene.grabDoc = PointerProperty(type=GRABDOC_property_group) + Collection.is_gd_collection = BoolProperty() + Object.is_gd_object = BoolProperty() # Set the updaters repo - updater.user="oRazeD" - updater.repo="grabdoc" - updater.current_version=bl_info["version"] + updater.user = "oRazeD" + updater.repo = "grabdoc" + updater.current_version = VERSION # Initial check for repo updates updater.check_for_update_now() diff --git a/render_setup_utils.py b/render_setup_utils.py deleted file mode 100644 index 21e1c40..0000000 --- a/render_setup_utils.py +++ /dev/null @@ -1,93 +0,0 @@ - -import bpy, bpy.types as types -from mathutils import Vector -from .constants import GlobalVariableConstants as GlobalVarConst - - -def is_in_viewing_frustrum(vec_check: Vector) -> bool: - """Decide whether a given object is within the cameras viewing frustrum""" - bg_plane = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] - - vec1 = Vector((bg_plane.dimensions.x * -1.25 + bg_plane.location[0], bg_plane.dimensions.y * -1.25 + bg_plane.location[1], -100)) - vec2 = Vector((bg_plane.dimensions.x * 1.25 + bg_plane.location[0], bg_plane.dimensions.y * 1.25 + bg_plane.location[1], 100)) - - for i in range(0, 3): - if (vec_check[i] < vec1[i] and vec_check[i] < vec2[i] or vec_check[i] > vec1[i] and vec_check[i] > vec2[i]): - return False - return True - - -def get_rendered_objects(context: types.Context) -> set: - """Generate a list of all objects that will be rendered based on its origin position in world space""" - rendered_obs = {GlobalVarConst.BG_PLANE_NAME} - - if context.scene.grabDoc.onlyRenderColl: - for coll in bpy.data.collections: - if coll.is_gd_collection: - for ob in coll.all_objects: - if ( - not ob.hide_render - and ob.type not in GlobalVarConst.UNRENDERABLE_TYPES - and not ob.is_gd_object - ): - rendered_obs.add(ob.name) - else: - for ob in context.view_layer.objects: - if ( - not ob.hide_render - and ob.type not in GlobalVarConst.UNRENDERABLE_TYPES - and not ob.is_gd_object - ): - local_bbox_center = .125 * sum((Vector(b) for b in ob.bound_box), Vector()) - global_bbox_center = ob.matrix_world @ local_bbox_center - - if is_in_viewing_frustrum(global_bbox_center): - rendered_obs.add(ob.name) - return rendered_obs - - -def find_tallest_object(self, context: types.Context) -> None: - """Find the tallest points in the viewlayer by looping through objects to find the highest vertex on the Z axis""" - depsgraph = context.evaluated_depsgraph_get() - - all_tallest_verts = [] - for ob in context.view_layer.objects: - if ob.name in self.rendered_obs and ob.type in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'} and not ob.name.startswith(GlobalVarConst.GD_PREFIX): - # Invoke to_mesh() for evaluated object. - ob_eval = ob.evaluated_get(depsgraph) - mesh_from_eval = ob_eval.to_mesh() - - # Get global coordinates of vertices - global_vert_coords = [ob_eval.matrix_world @ v.co for v in mesh_from_eval.vertices] - - # Find the highest Z value amongst the object's verts and then append it to list - if len(global_vert_coords): - max_z_coord = max([co.z for co in global_vert_coords]) - - all_tallest_verts.append(max_z_coord) - - # Remove temporary mesh. - ob_eval.to_mesh_clear() - - # Set the heights guide to the tallest found point - if len(all_tallest_verts): - context.scene.grabDoc.guideHeight = max(all_tallest_verts) - bpy.data.objects.get(GlobalVarConst.BG_PLANE_NAME).location[2] - - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### diff --git a/ui.py b/ui.py index 0fb730c..502e640 100644 --- a/ui.py +++ b/ui.py @@ -1,22 +1,29 @@ -import bpy, os, bpy.types as types +import os + +import bpy +from bpy.types import ( + Context, + Operator, + Panel, + UILayout, + Event +) -from .__init__ import bl_info from .preferences import GRABDOC_PT_presets -from .generic_utils import ( +from .constants import GlobalVariableConstants as Global +from .utils.generic import ( PanelInfo, SubPanelInfo, proper_scene_setup, is_camera_in_3d_view, - format_bl_version, + format_bl_label, is_pro_version ) -from .constants import GlobalVariableConstants as GlobalVarConst - -################################################################################################################ +################################################ # UI -################################################################################################################ +################################################ def warn_ui(layout): @@ -28,23 +35,29 @@ def warn_ui(layout): box.label(text='\u2022 No Marmoset Support', icon='BLANK1') -class GRABDOC_PT_grabdoc(PanelInfo, types.Panel): - bl_label = f'{bl_info["name"]} {format_bl_version()}' +class GRABDOC_PT_grabdoc(Panel, PanelInfo): + bl_label = format_bl_label() - def draw_header_preset(self, _context: types.Context): + def draw_header_preset(self, _context: Context): if proper_scene_setup(): + # NOTE: This method already has + # self but the IDE trips on it GRABDOC_PT_presets.draw_panel_header(self.layout) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout col = layout.column(align=True) row = col.row(align=True) - row.enabled = not grabDoc.modalState + row.enabled = not gd.modalState row.scale_y = 1.5 - row.operator("grab_doc.setup_scene", text="Refresh Scene" if proper_scene_setup() else "Setup Scene", icon="TOOL_SETTINGS") + row.operator( + "grab_doc.setup_scene", + text="Refresh Scene" if proper_scene_setup() else "Setup Scene", + icon="TOOL_SETTINGS" + ) if not proper_scene_setup(): return @@ -56,19 +69,19 @@ def draw(self, context: types.Context): row.scale_y = .95 row.prop( - grabDoc, "collSelectable", + gd, "collSelectable", text="Select", - icon='RESTRICT_SELECT_OFF' if grabDoc.collSelectable else 'RESTRICT_SELECT_ON' + icon='RESTRICT_SELECT_OFF' if gd.collSelectable else 'RESTRICT_SELECT_ON' ) row.prop( - grabDoc, "collVisible", + gd, "collVisible", text="Visible", - icon='RESTRICT_VIEW_OFF' if grabDoc.collVisible else 'RESTRICT_VIEW_ON' + icon='RESTRICT_VIEW_OFF' if gd.collVisible else 'RESTRICT_VIEW_ON' ) row.prop( - grabDoc, "collRendered", + gd, "collRendered", text="Render", - icon='RESTRICT_RENDER_OFF' if grabDoc.collRendered else 'RESTRICT_RENDER_ON' + icon='RESTRICT_RENDER_OFF' if gd.collRendered else 'RESTRICT_RENDER_ON' ) box = col.box() @@ -76,35 +89,35 @@ def draw(self, context: types.Context): box.use_property_decorate = False col = box.column() - col.prop(grabDoc, "scalingSet", text='Scaling', expand=True) + col.prop(gd, "scalingSet", text='Scaling', expand=True) col.separator(factor=.5) row = col.row() - row.prop(grabDoc, "widthFiltering", text="Filtering") + row.prop(gd, "widthFiltering", text="Filtering") row.separator() - row.prop(grabDoc, "useFiltering", text="") + row.prop(gd, "useFiltering", text="") col.separator() row = col.row() - row.enabled = not grabDoc.modalState - row.prop(grabDoc, "refSelection", text='Reference') + row.enabled = not gd.modalState + row.prop(gd, "refSelection", text='Reference') row.operator("grab_doc.load_ref", text="", icon='FILE_FOLDER') col.separator(factor=.5) row = col.row() - row.prop(grabDoc, "gridSubdivisions", text="Grid") + row.prop(gd, "gridSubdivisions", text="Grid") row.separator() - row.prop(grabDoc, "useGrid", text="") + row.prop(gd, "useGrid", text="") -class GRABDOC_PT_export(PanelInfo, types.Panel): +class GRABDOC_PT_export(PanelInfo, Panel): bl_label = 'Export Maps' bl_parent_id = "GRABDOC_PT_grabdoc" @classmethod - def poll(cls, _context: types.Context) -> bool: + def poll(cls, _context: Context) -> bool: return proper_scene_setup() - def marmoset_export_header_ui(self, layout: types.UILayout): + def marmoset_export_header_ui(self, layout: UILayout): user_prefs = bpy.context.preferences.addons[__package__].preferences marmo_exe = user_prefs.marmoEXE @@ -129,90 +142,94 @@ def marmoset_export_header_ui(self, layout: types.UILayout): text="Bake in Marmoset" if bpy.context.scene.grabDoc.marmoAutoBake else "Open in Marmoset", icon="EXPORT" ).send_type = 'open' - row.operator("grab_doc.bake_marmoset", text="", icon='FILE_REFRESH').send_type = 'refresh' + row.operator( + "grab_doc.bake_marmoset", + text="", + icon='FILE_REFRESH' + ).send_type = 'refresh' - def marmoset_ui(self, grabDoc, layout: types.UILayout): + def marmoset_ui(self, gd, layout: UILayout): col = layout.column(align=True) box = col.box() col2 = box.column() row = col2.row() - row.enabled = not grabDoc.modalState - row.prop(grabDoc, 'bakerType', text="Baker") + row.enabled = not gd.modalState + row.prop(gd, 'bakerType', text="Baker") col2.separator(factor=.5) row = col2.row() row.alert = not self.export_path_exists - row.prop(grabDoc, 'exportPath', text="Export Path") + row.prop(gd, 'exportPath', text="Export Path") row.alert = False row.operator("grab_doc.open_folder", text='', icon="FOLDER_REDIRECT") col2.separator(factor=.5) - col2.prop(grabDoc, "exportName", text="Name") + col2.prop(gd, "exportName", text="Name") col2.separator(factor=.5) row = col2.row() - row.prop(grabDoc, "exportResX", text='Resolution') - row.prop(grabDoc, "exportResY", text='') - row.prop(grabDoc, 'lockRes', icon_only=True, icon="LOCKED" if grabDoc.lockRes else "UNLOCKED") + row.prop(gd, "exportResX", text='Resolution') + row.prop(gd, "exportResY", text='') + row.prop(gd, 'lockRes', icon_only=True, icon="LOCKED" if gd.lockRes else "UNLOCKED") col2.separator(factor=.5) row = col2.row() - row.prop(grabDoc, "imageType_marmo") + row.prop(gd, "imageType_marmo") row.separator(factor=.5) row2 = row.row() - if grabDoc.imageType == "OPEN_EXR": - row2.prop(grabDoc, "colorDepthEXR", expand=True) - elif grabDoc.imageType != "TARGA" or grabDoc.bakerType == 'Marmoset': - row2.prop(grabDoc, "colorDepth", expand=True) + if gd.imageType == "OPEN_EXR": + row2.prop(gd, "colorDepthEXR", expand=True) + elif gd.imageType != "TARGA" or gd.bakerType == 'Marmoset': + row2.prop(gd, "colorDepth", expand=True) else: row2.enabled = False - row2.prop(grabDoc, "colorDepthTGA", expand=True) + row2.prop(gd, "colorDepthTGA", expand=True) col2.separator(factor=.5) row = col2.row(align=True) - row.prop(grabDoc, "marmoSamples", text="Samples", expand=True) + row.prop(gd, "marmoSamples", text="Samples", expand=True) box.use_property_split = False col = box.column(align=True) col.prop( - grabDoc, "onlyRenderColl", + gd, "useBakeCollection", text="Use Bake Group", - icon='CHECKBOX_HLT' if grabDoc.onlyRenderColl else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.useBakeCollection else 'CHECKBOX_DEHLT' ) col.prop( - grabDoc, "exportPlane", + gd, "exportPlane", text='Export Plane as FBX', - icon='CHECKBOX_HLT' if grabDoc.exportPlane else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.exportPlane else 'CHECKBOX_DEHLT' ) col.prop( - grabDoc, "openFolderOnExport", + gd, "openFolderOnExport", text="Open Folder on Export", - icon='CHECKBOX_HLT' if grabDoc.openFolderOnExport else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.openFolderOnExport else 'CHECKBOX_DEHLT' ) col = box.column(align=True) - col.prop(grabDoc, 'marmoAutoBake', text='Bake on Import', icon='CHECKBOX_HLT' if grabDoc.marmoAutoBake else 'CHECKBOX_DEHLT') + col.prop(gd, 'marmoAutoBake', text='Bake on Import', icon='CHECKBOX_HLT' if gd.marmoAutoBake else 'CHECKBOX_DEHLT') col = col.column(align=True) - col.enabled = True if grabDoc.marmoAutoBake else False + col.enabled = True if gd.marmoAutoBake else False col.prop( - grabDoc, + gd, 'marmoClosePostBake', text='Close Toolbag after Baking', - icon='CHECKBOX_HLT' if grabDoc.marmoClosePostBake else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.marmoClosePostBake else 'CHECKBOX_DEHLT' ) - def blender_ui(self, grabDoc, layout: types.UILayout): + def blender_ui(self, gd, layout: UILayout): col = layout.column(align=True) row = col.row(align=True) @@ -224,54 +241,54 @@ def blender_ui(self, grabDoc, layout: types.UILayout): if is_pro_version(): row = col2.row() - row.enabled = not grabDoc.modalState - row.prop(grabDoc, 'bakerType', text="Baker") + row.enabled = not gd.modalState + row.prop(gd, 'bakerType', text="Baker") col2.separator(factor=.5) row = col2.row() row.alert = not self.export_path_exists - row.prop(grabDoc, 'exportPath', text="Export Path") + row.prop(gd, 'exportPath', text="Export Path") row.alert = False row.operator("grab_doc.open_folder", text='', icon="FOLDER_REDIRECT") col2.separator(factor=.5) - col2.prop(grabDoc, "exportName", text="Name") + col2.prop(gd, "exportName", text="Name") col2.separator(factor=.5) row = col2.row() - row.prop(grabDoc, "exportResX", text='Resolution') - row.prop(grabDoc, "exportResY", text='') - row.prop(grabDoc, 'lockRes', icon_only=True, icon="LOCKED" if grabDoc.lockRes else "UNLOCKED") + row.prop(gd, "exportResX", text='Resolution') + row.prop(gd, "exportResY", text='') + row.prop(gd, 'lockRes', icon_only=True, icon="LOCKED" if gd.lockRes else "UNLOCKED") col2.separator(factor=.5) row = col2.row() - row.prop(grabDoc, "imageType") + row.prop(gd, "imageType") row.separator(factor=.5) row2 = row.row() - if grabDoc.imageType == "OPEN_EXR": - row2.prop(grabDoc, "colorDepthEXR", expand=True) - elif grabDoc.imageType != "TARGA" or grabDoc.bakerType == 'Marmoset': - row2.prop(grabDoc, "colorDepth", expand=True) + if gd.imageType == "OPEN_EXR": + row2.prop(gd, "colorDepthEXR", expand=True) + elif gd.imageType != "TARGA" or gd.bakerType == 'Marmoset': + row2.prop(gd, "colorDepth", expand=True) else: row2.enabled = False - row2.prop(grabDoc, "colorDepthTGA", expand=True) + row2.prop(gd, "colorDepthTGA", expand=True) col2.separator(factor=.5) - if grabDoc.imageType != "TARGA": + if gd.imageType != "TARGA": image_settings = bpy.context.scene.render.image_settings row = col2.row(align=True) - if grabDoc.imageType == "PNG": - row.prop(grabDoc, "imageCompPNG", text="Compression") - elif grabDoc.imageType == "OPEN_EXR": + if gd.imageType == "PNG": + row.prop(gd, "imageCompPNG", text="Compression") + elif gd.imageType == "OPEN_EXR": row.prop(image_settings, "exr_codec", text="Codec") else: # TIFF row.prop(image_settings, "tiff_codec", text="Codec") @@ -280,77 +297,77 @@ def blender_ui(self, grabDoc, layout: types.UILayout): col = box.column(align=True) col.prop( - grabDoc, "onlyRenderColl", + gd, "useBakeCollection", text="Use Bake Group", - icon='CHECKBOX_HLT' if grabDoc.onlyRenderColl else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.useBakeCollection else 'CHECKBOX_DEHLT' ) col.prop( - grabDoc, "openFolderOnExport", + gd, "openFolderOnExport", text="Open Folder on Export", - icon='CHECKBOX_HLT' if grabDoc.openFolderOnExport else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.openFolderOnExport else 'CHECKBOX_DEHLT' ) col.prop( - grabDoc, "exportPlane", + gd, "exportPlane", text='Export Plane as FBX', - icon='CHECKBOX_HLT' if grabDoc.exportPlane else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.exportPlane else 'CHECKBOX_DEHLT' ) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc - self.export_path_exists = os.path.exists(bpy.path.abspath(grabDoc.exportPath)) + def draw(self, context: Context): + gd = context.scene.grabDoc + self.export_path_exists = os.path.exists(bpy.path.abspath(gd.exportPath)) layout = self.layout layout.activate_init = True layout.use_property_split = True layout.use_property_decorate = False - if grabDoc.bakerType == 'Blender': - self.blender_ui(grabDoc, layout) + if gd.bakerType == 'Blender': + self.blender_ui(gd, layout) elif is_pro_version(): # Marmoset self.marmoset_export_header_ui(layout) - self.marmoset_ui(grabDoc, layout) + self.marmoset_ui(gd, layout) -class GRABDOC_OT_config_maps(types.Operator): +class GRABDOC_OT_config_maps(Operator): """Configure the UI visibility of maps, also disabling them from baking when hidden""" bl_idname = "grab_doc.config_maps" bl_label = "Configure Map Visibility" bl_options = {'REGISTER'} def execute(self, _context): - return {'FINISHED'} # NOTE Funky and neat + return {'FINISHED'} # NOTE: Funky and neat - def invoke(self, context: types.Context, _event: types.Event): + def invoke(self, context: Context, _event: Event): return context.window_manager.invoke_props_dialog(self, width = 200) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout - layout.prop(grabDoc, 'uiVisibilityNormals', text="Normals") - layout.prop(grabDoc, 'uiVisibilityCurvature', text="Curvature") - layout.prop(grabDoc, 'uiVisibilityOcclusion', text="Ambient Occlusion") - layout.prop(grabDoc, 'uiVisibilityHeight', text="Height") - layout.prop(grabDoc, 'uiVisibilityMatID', text="Material ID") - layout.prop(grabDoc, 'uiVisibilityAlpha', text="Alpha") - layout.prop(grabDoc, 'uiVisibilityAlbedo', text="Albedo (Blender Only)") - layout.prop(grabDoc, 'uiVisibilityRoughness', text="Roughness (Blender Only)") - layout.prop(grabDoc, 'uiVisibilityMetalness', text="Metalness (Blender Only)") - - -class GRABDOC_PT_view_edit_maps(PanelInfo, types.Panel): + layout.prop(gd, 'uiVisibilityNormals', text="Normals") + layout.prop(gd, 'uiVisibilityCurvature', text="Curvature") + layout.prop(gd, 'uiVisibilityOcclusion', text="Ambient Occlusion") + layout.prop(gd, 'uiVisibilityHeight', text="Height") + layout.prop(gd, 'uiVisibilityMatID', text="Material ID") + layout.prop(gd, 'uiVisibilityAlpha', text="Alpha") + layout.prop(gd, 'uiVisibilityAlbedo', text="Albedo (Blender Only)") + layout.prop(gd, 'uiVisibilityRoughness', text="Roughness (Blender Only)") + layout.prop(gd, 'uiVisibilityMetalness', text="Metalness (Blender Only)") + + +class GRABDOC_PT_view_edit_maps(PanelInfo, Panel): bl_label = 'Edit Maps' bl_parent_id = "GRABDOC_PT_grabdoc" @classmethod - def poll(cls, _context: types.Context) -> bool: + def poll(cls, _context: Context) -> bool: return proper_scene_setup() - def draw_header_preset(self, _context: types.Context): + def draw_header_preset(self, _context: Context): self.layout.operator("grab_doc.config_maps", emboss=False, text="", icon="SETTINGS") - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout col = layout.column(align=True) @@ -358,11 +375,20 @@ def draw(self, context: types.Context): row = col.row(align=True) row.scale_y = 1.25 in_trim_cam = is_camera_in_3d_view() - row.operator("grab_doc.view_cam", text="Leave Camera View" if in_trim_cam else "View GrabDoc Camera", icon="OUTLINER_OB_CAMERA") + row.operator( + "grab_doc.view_cam", + text="Leave Camera View" if in_trim_cam else "View GrabDoc Camera", + icon="OUTLINER_OB_CAMERA" + ) - col.prop(grabDoc, 'autoExitCamera', text="Leave Cam on Preview Exit", icon='CHECKBOX_HLT' if grabDoc.autoExitCamera else 'CHECKBOX_DEHLT') + col.prop( + gd, + 'autoExitCamera', + text="Leave Cam on Preview Exit", + icon='CHECKBOX_HLT' if gd.autoExitCamera else 'CHECKBOX_DEHLT' + ) - if not grabDoc.modalState: + if not gd.modalState: return col.separator() @@ -373,135 +399,148 @@ def draw(self, context: types.Context): row = col.row(align=True) row.scale_y = 1.1 - mat_preview_type = "Material ID" if grabDoc.modalPreviewType == 'ID' else grabDoc.modalPreviewType.capitalize() + mat_preview_type = "Material ID" if gd.modalPreviewType == 'ID' else gd.modalPreviewType.capitalize() row.operator("grab_doc.export_preview", text = f"Export {mat_preview_type}", icon="EXPORT") - if grabDoc.modalPreviewType == 'normals': + if gd.modalPreviewType == 'normals': GRABDOC_PT_normals_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'curvature': + elif gd.modalPreviewType == 'curvature': GRABDOC_PT_curvature_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'occlusion': + elif gd.modalPreviewType == 'occlusion': GRABDOC_PT_occlusion_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'height': + elif gd.modalPreviewType == 'height': GRABDOC_PT_height_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'ID': + elif gd.modalPreviewType == 'ID': GRABDOC_PT_id_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'alpha': + elif gd.modalPreviewType == 'alpha': GRABDOC_PT_alpha_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'albedo': + elif gd.modalPreviewType == 'albedo': GRABDOC_PT_albedo_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'roughness': + elif gd.modalPreviewType == 'roughness': GRABDOC_PT_roughness_settings.draw(self, context) - elif grabDoc.modalPreviewType == 'metalness': + elif gd.modalPreviewType == 'metalness': GRABDOC_PT_metalness_settings.draw(self, context) -class GRABDOC_PT_pack_maps(PanelInfo, types.Panel): - bl_label = 'Pack Maps' - bl_parent_id = "GRABDOC_PT_grabdoc" - - @classmethod - def poll(cls, context: types.Context) -> bool: - return proper_scene_setup() and not context.scene.grabDoc.modalState +#class GRABDOC_PT_pack_maps(PanelInfo, Panel): +# bl_label = 'Pack Maps' +# bl_parent_id = "GRABDOC_PT_grabdoc" - def draw_header_preset(self, _context: types.Context): - self.layout.operator("grab_doc.map_pack_info", emboss=False, text="", icon="HELP") +# @classmethod +# def poll(cls, context: Context) -> bool: +# return proper_scene_setup() and not context.scene.grabDoc.modalState - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc +# def draw_header_preset(self, _context: Context): +# self.layout.operator( +# "grab_doc.map_pack_info", +# emboss=False, +# text="", +# icon="HELP" +# ) - row = self.layout.row(align=True) - row.prop(grabDoc, 'packMaps', text='') - row.separator(factor=.5) +# def draw_header(self, context: Context): +# gd = context.scene.grabDoc +# +# row = self.layout.row(align=True) +# row.prop(gd, 'packMaps', text='') +# row.separator(factor=.5) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc +# def draw(self, context: Context): +# gd = context.scene.grabDoc - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False +# layout = self.layout +# layout.use_property_split = True +# layout.use_property_decorate = False - col = layout.column(align=True) - col.prop(grabDoc, 'mapType_R') - col.prop(grabDoc, 'mapType_G') - col.prop(grabDoc, 'mapType_B') - col.prop(grabDoc, 'mapType_A') +# col = layout.column(align=True) +# col.prop(gd, 'channel_R') +# col.prop(gd, 'channel_G') +# col.prop(gd, 'channel_B') +# col.prop(gd, 'channel_A') -class GRABDOC_PT_normals_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_normals_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityNormals + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityNormals - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportNormals', text="") + row.prop(gd, 'exportNormals', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Normals Preview" ).map_types = 'normals' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'normals' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'normals' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() - col.prop(grabDoc, "engineNormals", text="Engine") + col.prop(gd, "engineNormals", text="Engine") col = layout.column() - col.prop(grabDoc, 'flipYNormals', text="Flip Y (-Y)") + col.prop(gd, 'flipYNormals', text="Flip Y (-Y)") - if grabDoc.bakerType == 'Blender': + if gd.bakerType == 'Blender': col.separator(factor=.5) - col.prop(grabDoc, 'useTextureNormals', text="Texture Normals") + col.prop(gd, 'useTextureNormals', text="Texture Normals") col.separator(factor=.5) - col.prop(grabDoc, 'reimportAsMatNormals', text="Import as Material") + col.prop(gd, 'reimportAsMatNormals', text="Import as Material") col.separator(factor=1.5) col.prop( - grabDoc, - "samplesNormals" if grabDoc.engineNormals == 'blender_eevee' else "samplesCyclesNormals", + gd, + "samplesNormals" if gd.engineNormals == 'blender_eevee' else "samplesCyclesNormals", text='Samples' ) col.separator(factor=.5) - col.prop(grabDoc, 'suffixNormals', text="Suffix") + col.prop(gd, 'suffixNormals', text="Suffix") -class GRABDOC_PT_curvature_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_curvature_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityCurvature + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityCurvature - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportCurvature', text="") + row.prop(gd, 'exportCurvature', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Curvature Preview" ).map_types = 'curvature' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'curvature' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'curvature' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True @@ -509,42 +548,46 @@ def draw(self, context: types.Context): col = layout.column() - if grabDoc.bakerType == 'Blender': - col.prop(grabDoc, 'ridgeCurvature', text="Ridge") + if gd.bakerType == 'Blender': + col.prop(gd, 'ridgeCurvature', text="Ridge") col.separator(factor=.5) - col.prop(grabDoc, 'valleyCurvature', text="Valley") + col.prop(gd, 'valleyCurvature', text="Valley") col.separator(factor=1.5) - col.prop(grabDoc, "samplesCurvature", text="Samples") + col.prop(gd, "samplesCurvature", text="Samples") col.separator(factor=.5) - col.prop(grabDoc, 'contrastCurvature', text="Contrast") + col.prop(gd, 'contrastCurvature', text="Contrast") col.separator(factor=.5) - col.prop(grabDoc, 'suffixCurvature', text="Suffix") + col.prop(gd, 'suffixCurvature', text="Suffix") -class GRABDOC_PT_occlusion_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_occlusion_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityOcclusion + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityOcclusion - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportOcclusion', text="") + row.prop(gd, 'exportOcclusion', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Occlusion Preview" ).map_types = 'occlusion' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'occlusion' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'occlusion' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True @@ -552,48 +595,52 @@ def draw(self, context: types.Context): col = layout.column() - if grabDoc.bakerType == 'Marmoset': - col.prop(grabDoc, "marmoAORayCount", text="Ray Count") + if gd.bakerType == 'Marmoset': + col.prop(gd, "marmoAORayCount", text="Ray Count") else: # Blender - col.prop(grabDoc, 'reimportAsMatOcclusion', text="Import as Material") + col.prop(gd, 'reimportAsMatOcclusion', text="Import as Material") col.separator(factor=.5) - col.prop(grabDoc, 'gammaOcclusion', text="Intensity") + col.prop(gd, 'gammaOcclusion', text="Intensity") col.separator(factor=.5) - col.prop(grabDoc, 'distanceOcclusion', text="Distance") + col.prop(gd, 'distanceOcclusion', text="Distance") col.separator(factor=1.5) - col.prop(grabDoc, "samplesOcclusion", text="Samples") + col.prop(gd, "samplesOcclusion", text="Samples") col.separator(factor=.5) - col.prop(grabDoc, 'contrastOcclusion', text="Contrast") + col.prop(gd, 'contrastOcclusion', text="Contrast") col.separator(factor=.5) - col.prop(grabDoc, 'suffixOcclusion', text="Suffix") + col.prop(gd, 'suffixOcclusion', text="Suffix") -class GRABDOC_PT_height_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_height_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityHeight + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityHeight - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportHeight', text="") + row.prop(gd, 'exportHeight', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Height Preview" ).map_types = 'height' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'height' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'height' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True @@ -601,50 +648,54 @@ def draw(self, context: types.Context): col = layout.column() - if grabDoc.bakerType == 'Blender': - col.prop(grabDoc, 'invertMaskHeight', text="Invert Mask") + if gd.bakerType == 'Blender': + col.prop(gd, 'invertMaskHeight', text="Invert Mask") col.separator(factor=.5) row = col.row() - row.prop(grabDoc, "rangeTypeHeight", text='Height Mode', expand=True) + row.prop(gd, "rangeTypeHeight", text='Height Mode', expand=True) - if grabDoc.rangeTypeHeight == 'MANUAL': + if gd.rangeTypeHeight == 'MANUAL': col.separator(factor=.5) - col.prop(grabDoc, 'guideHeight', text="0-1 Range") + col.prop(gd, 'guideHeight', text="0-1 Range") - if grabDoc.bakerType == 'Blender': + if gd.bakerType == 'Blender': col.separator(factor=1.5) - col.prop(grabDoc, "samplesHeight", text="Samples") + col.prop(gd, "samplesHeight", text="Samples") col.separator(factor=.5) - col.prop(grabDoc, 'contrastHeight', text="Contrast") + col.prop(gd, 'contrastHeight', text="Contrast") col.separator(factor=.5) - col.prop(grabDoc, 'suffixHeight', text="Suffix") + col.prop(gd, 'suffixHeight', text="Suffix") -class GRABDOC_PT_id_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_id_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityMatID + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityMatID - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportMatID', text="") + row.prop(gd, 'exportMatID', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Mat ID Preview" ).map_types = 'ID' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'ID' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'ID' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True @@ -653,13 +704,13 @@ def draw(self, context: types.Context): col = layout.column() row = col.row() - if grabDoc.bakerType == 'Marmoset': + if gd.bakerType == 'Marmoset': row.enabled = False - row.prop(grabDoc, "fakeMethodMatID", text="ID Method") + row.prop(gd, "fakeMethodMatID", text="ID Method") else: - row.prop(grabDoc, "methodMatID", text="ID Method") + row.prop(gd, "methodMatID", text="ID Method") - if grabDoc.methodMatID == "MATERIAL" or grabDoc.bakerType == 'Marmoset': + if gd.methodMatID == "MATERIAL" or gd.bakerType == 'Marmoset': col = layout.column(align=True) col.separator(factor=.5) col.scale_y = 1.1 @@ -668,7 +719,7 @@ def draw(self, context: types.Context): row = col.row(align=True) row.scale_y = .9 row.label(text=" Remove:") - row.operator("grab_doc.remove_mats_by_name", text='All').mat_name = GlobalVarConst.MAT_ID_RAND_PREFIX + row.operator("grab_doc.remove_mats_by_name", text='All').mat_name = Global.MAT_ID_RAND_PREFIX col = layout.column(align=True) col.separator(factor=.5) @@ -678,40 +729,44 @@ def draw(self, context: types.Context): row = col.row(align=True) row.scale_y = .9 row.label(text=" Remove:") - row.operator("grab_doc.remove_mats_by_name", text='All').mat_name = GlobalVarConst.MAT_ID_PREFIX + row.operator("grab_doc.remove_mats_by_name", text='All').mat_name = Global.MAT_ID_PREFIX row.operator("grab_doc.quick_remove_selected_mats", text='Selected') - if grabDoc.bakerType == 'Blender': + if gd.bakerType == 'Blender': col.separator(factor=1.5) - col.prop(grabDoc, "samplesMatID", text="Samples") + col.prop(gd, "samplesMatID", text="Samples") col.separator(factor=.5) - col.prop(grabDoc, 'suffixID', text="Suffix") + col.prop(gd, 'suffixID', text="Suffix") -class GRABDOC_PT_alpha_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_alpha_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityAlpha + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityAlpha - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportAlpha', text="") + row.prop(gd, 'exportAlpha', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Alpha Preview" ).map_types = 'alpha' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'alpha' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'alpha' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout layout.use_property_split = True @@ -719,38 +774,42 @@ def draw(self, context: types.Context): col = layout.column() - if grabDoc.bakerType == 'Blender': - col.prop(grabDoc, 'invertMaskAlpha', text="Invert Mask") + if gd.bakerType == 'Blender': + col.prop(gd, 'invertMaskAlpha', text="Invert Mask") col.separator(factor=1.5) - col.prop(grabDoc, 'samplesAlpha', text="Samples") + col.prop(gd, 'samplesAlpha', text="Samples") col.separator(factor=.5) - col.prop(grabDoc, 'suffixAlpha', text="Suffix") + col.prop(gd, 'suffixAlpha', text="Suffix") -class GRABDOC_PT_albedo_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_albedo_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityAlbedo and grabDoc.bakerType == 'Blender' + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityAlbedo and gd.bakerType == 'Blender' - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportAlbedo', text="") + row.prop(gd, 'exportAlbedo', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Albedo Preview" ).map_types = 'albedo' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'albedo' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'albedo' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout @@ -760,42 +819,46 @@ def draw(self, context: types.Context): layout.use_property_decorate = False col = layout.column() - col.prop(grabDoc, "engineAlbedo", text="Engine") + col.prop(gd, "engineAlbedo", text="Engine") col.separator(factor=1.5) col.prop( - grabDoc, - "samplesAlbedo" if grabDoc.engineAlbedo == 'blender_eevee' else "samplesCyclesAlbedo", + gd, + "samplesAlbedo" if gd.engineAlbedo == 'blender_eevee' else "samplesCyclesAlbedo", text='Samples' ) col.separator(factor=.5) - col.prop(grabDoc, 'suffixAlbedo', text="Suffix") + col.prop(gd, 'suffixAlbedo', text="Suffix") -class GRABDOC_PT_roughness_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_roughness_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityRoughness and grabDoc.bakerType == 'Blender' + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityRoughness and gd.bakerType == 'Blender' - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportRoughness', text="") + row.prop(gd, 'exportRoughness', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Roughness Preview" ).map_types = 'roughness' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'roughness' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'roughness' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout @@ -805,47 +868,51 @@ def draw(self, context: types.Context): layout.use_property_decorate = False col = layout.column() - col.prop(grabDoc, "engineRoughness", text="Engine") + col.prop(gd, "engineRoughness", text="Engine") col.separator(factor=.5) - if grabDoc.bakerType == 'Blender': - col.prop(grabDoc, 'invertMaskRoughness', text="Invert") + if gd.bakerType == 'Blender': + col.prop(gd, 'invertMaskRoughness', text="Invert") col.separator(factor=.5) col.separator(factor=1.5) col.prop( - grabDoc, - "samplesRoughness" if grabDoc.engineRoughness == 'blender_eevee' else "samplesCyclesRoughness", + gd, + "samplesRoughness" if gd.engineRoughness == 'blender_eevee' else "samplesCyclesRoughness", text='Samples' ) col.separator(factor=.5) - col.prop(grabDoc, 'suffixRoughness', text="Suffix") + col.prop(gd, 'suffixRoughness', text="Suffix") -class GRABDOC_PT_metalness_settings(PanelInfo, SubPanelInfo, types.Panel): +class GRABDOC_PT_metalness_settings(PanelInfo, SubPanelInfo, Panel): @classmethod - def poll(cls, context: types.Context) -> bool: - grabDoc = context.scene.grabDoc - return not grabDoc.modalState and grabDoc.uiVisibilityMetalness and grabDoc.bakerType == 'Blender' + def poll(cls, context: Context) -> bool: + gd = context.scene.grabDoc + return not gd.modalState and gd.uiVisibilityMetalness and gd.bakerType == 'Blender' - def draw_header(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw_header(self, context: Context): + gd = context.scene.grabDoc row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(grabDoc, 'exportMetalness', text="") + row.prop(gd, 'exportMetalness', text="") row.operator( - "grab_doc.preview_warning" if grabDoc.firstBakePreview else "grab_doc.preview_map", + "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", text="Metalness Preview" ).map_types = 'metalness' - row.operator("grab_doc.offline_render", text="", icon="RENDER_STILL").map_types = 'metalness' + row.operator( + "grab_doc.offline_render", + text="", + icon="RENDER_STILL" + ).map_types = 'metalness' row.separator(factor=1.3) - def draw(self, context: types.Context): - grabDoc = context.scene.grabDoc + def draw(self, context: Context): + gd = context.scene.grabDoc layout = self.layout @@ -855,22 +922,22 @@ def draw(self, context: types.Context): layout.use_property_decorate = False col = layout.column() - col.prop(grabDoc, "engineMetalness", text="Engine") + col.prop(gd, "engineMetalness", text="Engine") col.separator(factor=1.5) col.prop( - grabDoc, - "samplesMetalness" if grabDoc.engineMetalness == 'blender_eevee' else "samplesCyclesMetalness", + gd, + "samplesMetalness" if gd.engineMetalness == 'blender_eevee' else "samplesCyclesMetalness", text='Samples' ) col.separator(factor=.5) - col.prop(grabDoc, 'suffixMetalness', text="Suffix") + col.prop(gd, 'suffixMetalness', text="Suffix") -################################################################################################################ +################################################ # REGISTRATION -################################################################################################################ +################################################ classes = ( @@ -878,7 +945,7 @@ def draw(self, context: types.Context): GRABDOC_OT_config_maps, GRABDOC_PT_export, GRABDOC_PT_view_edit_maps, - GRABDOC_PT_pack_maps, + #GRABDOC_PT_pack_maps, GRABDOC_PT_normals_settings, GRABDOC_PT_curvature_settings, GRABDOC_PT_occlusion_settings, diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/baker_setup_cleanup_utils.py b/utils/baker.py similarity index 65% rename from baker_setup_cleanup_utils.py rename to utils/baker.py index d78f616..ceaeff3 100644 --- a/baker_setup_cleanup_utils.py +++ b/utils/baker.py @@ -1,17 +1,23 @@ -import bpy, os, bpy.types as types -from .node_group_utils import add_ng_to_mat -from .render_setup_utils import find_tallest_object -from .generic_utils import get_format_extension -from .constants import GlobalVariableConstants as GlobalVarConst +import os -################################################################################################################ +import bpy +from bpy.types import Context + +from ..constants import GlobalVariableConstants as Global +from .node import add_ng_to_mat +from .generic import get_format_extension +from .render import set_guide_height + + +################################################ # BAKER SETUP & CLEANUP -################################################################################################################ +################################################ -def set_color_management_settings(display_device: str='None'): - """Helper function for supporting AGX and other obscure color management profiles (ignoring anything that isn't compatible)""" +def set_color_management(display_device: str='None') -> None: + """Helper function for supporting custom color management + profiles. Ignores anything that isn't compatible""" display_settings = bpy.context.scene.display_settings view_settings = bpy.context.scene.view_settings @@ -30,7 +36,6 @@ def set_color_management_settings(display_device: str='None'): pass else: display_settings.display_device = display_device - view_settings.view_transform = 'Standard' view_settings.look = 'None' @@ -38,12 +43,12 @@ def set_color_management_settings(display_device: str='None'): view_settings.gamma = 1 -def export_and_preview_setup(self, context: types.Context): +def export_and_preview_setup(self, context: Context): scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc render = scene.render - # TODO Preserve use_local_camera & original camera + # TODO: Preserve use_local_camera & original camera # Set - Active Camera for area in context.screen.areas: @@ -52,7 +57,7 @@ def export_and_preview_setup(self, context: types.Context): space.use_local_camera = False break - scene.camera = bpy.data.objects.get(GlobalVarConst.TRIM_CAMERA_NAME) + scene.camera = bpy.data.objects.get(Global.TRIM_CAMERA_NAME) ## VIEW LAYER PROPERTIES ## @@ -108,7 +113,7 @@ def export_and_preview_setup(self, context: types.Context): self.savedGamma = view_settings.gamma self.savedTransparency = render.film_transparent - set_color_management_settings('sRGB') + set_color_management('sRGB') # Save & Set - Performance if bpy.app.version >= (2, 83, 0): @@ -117,7 +122,7 @@ def export_and_preview_setup(self, context: types.Context): # Save & Set - Film self.savedFilterSize = render.filter_size - render.filter_size = grabDoc.widthFiltering + render.filter_size = gd.widthFiltering self.savedFilterSizeCycles = context.scene.cycles.filter_width self.savedFilterSizeTypeCycles = context.scene.cycles.pixel_filter_type @@ -129,36 +134,37 @@ def export_and_preview_setup(self, context: types.Context): image_settings = render.image_settings # Set - Dimensions (Don't bother saving these) - render.resolution_x = grabDoc.exportResX - render.resolution_y = grabDoc.exportResY + render.resolution_x = gd.exportResX + render.resolution_y = gd.exportResY render.resolution_percentage = 100 # Save & Set - Output self.savedColorMode = image_settings.color_mode self.savedFileFormat = image_settings.file_format - if not grabDoc.collRendered: # If background plane not visible in render, create alpha channel + # If background plane not visible in render, create alpha channel + if not gd.collRendered: render.film_transparent = True image_settings.color_mode = 'RGBA' else: image_settings.color_mode = 'RGB' - image_settings.file_format = grabDoc.imageType + image_settings.file_format = gd.imageType - if grabDoc.imageType == 'OPEN_EXR': - image_settings.color_depth = grabDoc.colorDepthEXR - elif grabDoc.imageType != 'TARGA': - image_settings.color_depth = grabDoc.colorDepth + if gd.imageType == 'OPEN_EXR': + image_settings.color_depth = gd.colorDepthEXR + elif gd.imageType != 'TARGA': + image_settings.color_depth = gd.colorDepth - if grabDoc.imageType == "PNG": - image_settings.compression = grabDoc.imageCompPNG + if gd.imageType == "PNG": + image_settings.compression = gd.imageCompPNG self.savedColorDepth = image_settings.color_depth # Save & Set - Post Processing self.savedUseSequencer = render.use_sequencer - self.savedUseCompositer = render.use_compositing + self.savedUseCompositor = render.use_compositing self.savedDitherIntensity = render.dither_intensity render.use_sequencer = render.use_compositing = False @@ -179,26 +185,28 @@ def export_and_preview_setup(self, context: types.Context): self.savedOutline = scene_shading.show_object_outline self.savedShowSpec = scene_shading.show_specular_highlight - scene_shading.show_backface_culling = scene_shading.show_xray = scene_shading.show_shadows = False - scene_shading.show_cavity = scene_shading.use_dof = scene_shading.show_object_outline = False + scene_shading.show_backface_culling = \ + scene_shading.show_xray = scene_shading.show_shadows = False + scene_shading.show_cavity = \ + scene_shading.use_dof = scene_shading.show_object_outline = False scene_shading.show_specular_highlight = False ## PLANE REFERENCE ## - self.savedRefSelection = grabDoc.refSelection.name if grabDoc.refSelection else None + self.savedRefSelection = gd.refSelection.name if gd.refSelection else None ## OBJECT VISIBILITY ## - bg_plane = bpy.data.objects.get(GlobalVarConst.BG_PLANE_NAME) + bg_plane = bpy.data.objects.get(Global.BG_PLANE_NAME) - bg_plane.hide_viewport = not grabDoc.collVisible - bg_plane.hide_render = not grabDoc.collRendered + bg_plane.hide_viewport = not gd.collVisible + bg_plane.hide_render = not gd.collRendered bg_plane.hide_set(False) -def export_refresh(self, context: types.Context) -> None: +def export_refresh(self, context: Context) -> None: scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc render = scene.render ## VIEW LAYER PROPERTIES ## @@ -259,7 +267,7 @@ def export_refresh(self, context: types.Context) -> None: # Refresh - Post Processing render.use_sequencer = self.savedUseSequencer - render.use_compositing = self.savedUseCompositer + render.use_compositing = self.savedUseCompositor render.dither_intensity = self.savedDitherIntensity @@ -281,16 +289,17 @@ def export_refresh(self, context: types.Context) -> None: ## PLANE REFERENCE ## if self.savedRefSelection: - grabDoc.refSelection = bpy.data.images[self.savedRefSelection] + gd.refSelection = bpy.data.images[self.savedRefSelection] def reimport_as_material(suffix) -> None: """Reimport an exported map as a material for further use inside of Blender""" - grabDoc = bpy.context.scene.grabDoc + gd = bpy.context.scene.grabDoc - mat_name = f'{grabDoc.exportName}_{suffix}' + mat_name = f'{gd.exportName}_{suffix}' - # TODO don't remove the material and start from scratch, but replace/update the image? + # TODO: don't remove the material and start + # from scratch, but replace/update the image? # Remove pre-existing material if mat_name in bpy.data.materials: @@ -304,75 +313,90 @@ def reimport_as_material(suffix) -> None: mat = bpy.data.materials.new(name=mat_name) mat.use_nodes = True - output_node = mat.node_tree.nodes["Material Output"] - output_node.location = (0,0) + output = mat.node_tree.nodes["Material Output"] + output.location = (0,0) mat.node_tree.nodes.remove(mat.node_tree.nodes.get('Principled BSDF')) file_extension = get_format_extension() - image_node = mat.node_tree.nodes.new('ShaderNodeTexImage') - image_node.image = bpy.data.images.load(os.path.join(bpy.path.abspath(grabDoc.exportPath), mat_name + file_extension)) - image_node.name = mat_name - image_node.location = (-300,0) + image = mat.node_tree.nodes.new('ShaderNodeTexImage') + image.image = bpy.data.images.load( + os.path.join(bpy.path.abspath(gd.exportPath),mat_name + file_extension) + ) + image.name = mat_name + image.location = (-300,0) # Context specific image settings (currently only uses Non-Color) - image_node.image.colorspace_settings.name = 'Non-Color' + image.image.colorspace_settings.name = 'Non-Color' # Make links - mat.node_tree.links.new(output_node.inputs['Surface'], image_node.outputs['Color']) + mat.node_tree.links.new(output.inputs['Surface'], image.outputs['Color']) -################################################################################################################ +################################################ # INDIVIDUAL MATERIAL SETUP & CLEANUP -################################################################################################################ +################################################ # NORMALS -def normals_setup(self, context: types.Context) -> None: +def normals_setup(self, context: Context) -> None: scene = context.scene render = scene.render if scene.grabDoc.engineNormals == 'blender_eevee': - scene.eevee.taa_render_samples = scene.eevee.taa_samples = scene.grabDoc.samplesNormals + scene.eevee.taa_render_samples = \ + scene.eevee.taa_samples = scene.grabDoc.samplesNormals else: # Cycles - scene.cycles.samples = scene.cycles.preview_samples = scene.grabDoc.samplesCyclesNormals + scene.cycles.samples = \ + scene.cycles.preview_samples = scene.grabDoc.samplesCyclesNormals render.engine = str(scene.grabDoc.engineNormals).upper() - set_color_management_settings('None') - - ng_normal = bpy.data.node_groups[GlobalVarConst.NG_NORMAL_NAME] - vec_transform_node = ng_normal.nodes.get('Vector Transform') - group_output_node = ng_normal.nodes.get('Group Output') + set_color_management('None') - link = ng_normal.links + ng_normal = bpy.data.node_groups[Global.NG_NORMAL_NAME] + vec_transform = ng_normal.nodes.get('Vector Transform') + group_output = ng_normal.nodes.get('Group Output') + links = ng_normal.links if context.scene.grabDoc.useTextureNormals: - link.new(vec_transform_node.inputs["Vector"], ng_normal.nodes.get('Bevel').outputs["Normal"]) - link.new(group_output_node.inputs["Output"], ng_normal.nodes.get('Mix Shader').outputs["Shader"]) + links.new( + vec_transform.inputs["Vector"], + ng_normal.nodes.get('Bevel').outputs["Normal"] + ) + links.new( + group_output.inputs["Output"], + ng_normal.nodes.get('Mix Shader').outputs["Shader"] + ) else: - link.new(vec_transform_node.inputs["Vector"], ng_normal.nodes.get('Bevel.001').outputs["Normal"]) - link.new(group_output_node.inputs["Output"], ng_normal.nodes.get('Vector Math.001').outputs["Vector"]) + links.new( + vec_transform.inputs["Vector"], + ng_normal.nodes.get('Bevel.001').outputs["Normal"] + ) + links.new( + group_output.inputs["Output"], + ng_normal.nodes.get('Vector Math.001').outputs["Vector"] + ) - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_NORMAL_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_NORMAL_NAME) # CURVATURE -def curvature_setup(self, context: types.Context) -> None: +def curvature_setup(self, context: Context) -> None: scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc scene_shading = bpy.data.scenes[str(scene.name)].display.shading # Set - Render engine settings scene.render.engine = 'BLENDER_WORKBENCH' - scene.display.render_aa = scene.display.viewport_aa = grabDoc.samplesCurvature + scene.display.render_aa = scene.display.viewport_aa = gd.samplesCurvature scene_shading.light = 'FLAT' scene_shading.color_type = 'SINGLE' - set_color_management_settings('sRGB') + set_color_management('sRGB') try: - scene.view_settings.look = grabDoc.contrastCurvature.replace('_', ' ') + scene.view_settings.look = gd.contrastCurvature.replace('_', ' ') except TypeError: pass @@ -388,15 +412,16 @@ def curvature_setup(self, context: types.Context) -> None: scene_shading.show_cavity = True scene_shading.cavity_type = 'BOTH' - scene_shading.cavity_ridge_factor = scene_shading.curvature_ridge_factor = grabDoc.ridgeCurvature - scene_shading.curvature_valley_factor = grabDoc.valleyCurvature + scene_shading.cavity_ridge_factor = \ + scene_shading.curvature_ridge_factor = gd.ridgeCurvature + scene_shading.curvature_valley_factor = gd.valleyCurvature scene_shading.cavity_valley_factor = 0 scene_shading.single_color = (.214041, .214041, .214041) scene.display.matcap_ssao_distance = .075 -def curvature_refresh(self, context: types.Context) -> None: +def curvature_refresh(self, context: Context) -> None: scene_shading = bpy.data.scenes[str(context.scene.name)].display.shading scene_shading.cavity_ridge_factor = self.savedCavityRidgeFactor @@ -409,21 +434,21 @@ def curvature_refresh(self, context: types.Context) -> None: context.scene.display.matcap_ssao_distance = self.savedRidgeDistance - bpy.data.objects[GlobalVarConst.BG_PLANE_NAME].color[3] = 1 + bpy.data.objects[Global.BG_PLANE_NAME].color[3] = 1 # AMBIENT OCCLUSION -def occlusion_setup(self, context: types.Context) -> None: +def occlusion_setup(self, context: Context) -> None: scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc eevee = scene.eevee scene.render.engine = 'BLENDER_EEVEE' - eevee.taa_render_samples = eevee.taa_samples = grabDoc.samplesOcclusion - set_color_management_settings('None') + eevee.taa_render_samples = eevee.taa_samples = gd.samplesOcclusion + set_color_management('None') try: - scene.view_settings.look = grabDoc.contrastOcclusion.replace('_', ' ') + scene.view_settings.look = gd.contrastOcclusion.replace('_', ' ') except TypeError: pass @@ -437,10 +462,10 @@ def occlusion_setup(self, context: types.Context) -> None: # Set - Ambient Occlusion eevee.use_gtao = True - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_AO_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_AO_NAME) -def occlusion_refresh(self, context: types.Context) -> None: +def occlusion_refresh(self, context: Context) -> None: eevee = context.scene.eevee eevee.use_overscan = self.savedUseOverscan @@ -452,102 +477,109 @@ def occlusion_refresh(self, context: types.Context) -> None: # HEIGHT -def height_setup(self, context: types.Context) -> None: +def height_setup(self, context: Context) -> None: scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc scene.render.engine = 'BLENDER_EEVEE' - scene.eevee.taa_render_samples = scene.eevee.taa_samples = grabDoc.samplesHeight - set_color_management_settings('None') + scene.eevee.taa_render_samples = scene.eevee.taa_samples = gd.samplesHeight + set_color_management('None') try: - scene.view_settings.look = grabDoc.contrastHeight.replace('_', ' ') + scene.view_settings.look = gd.contrastHeight.replace('_', ' ') except TypeError: pass - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_HEIGHT_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_HEIGHT_NAME) - if grabDoc.rangeTypeHeight == 'AUTO': - find_tallest_object(self, context) + if gd.rangeTypeHeight == 'AUTO': + set_guide_height() # MATERIAL ID -def id_setup(self, context: types.Context) -> None: +def id_setup(_self, context: Context) -> None: scene = context.scene - grabDoc = scene.grabDoc + gd = scene.grabDoc render = scene.render scene_shading = bpy.data.scenes[str(scene.name)].display.shading render.engine = 'BLENDER_WORKBENCH' - scene.display.render_aa = scene.display.viewport_aa = grabDoc.samplesMatID + scene.display.render_aa = scene.display.viewport_aa = gd.samplesMatID scene_shading.light = 'FLAT' - set_color_management_settings('sRGB') + set_color_management('sRGB') # Choose the method of ID creation based on user preference - scene_shading.color_type = grabDoc.methodMatID + scene_shading.color_type = gd.methodMatID # ALPHA -def alpha_setup(self, context: types.Context) -> None: +def alpha_setup(self, context: Context) -> None: scene = context.scene render = scene.render render.engine = 'BLENDER_EEVEE' - scene.eevee.taa_render_samples = scene.eevee.taa_samples = scene.grabDoc.samplesAlpha - set_color_management_settings('None') + scene.eevee.taa_render_samples = \ + scene.eevee.taa_samples = scene.grabDoc.samplesAlpha + set_color_management('None') - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_ALPHA_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_ALPHA_NAME) # ALBEDO -def albedo_setup(self, context: types.Context) -> None: +def albedo_setup(self, context: Context) -> None: scene = context.scene render = scene.render if scene.grabDoc.engineAlbedo == 'blender_eevee': - scene.eevee.taa_render_samples = scene.eevee.taa_samples = scene.grabDoc.samplesAlbedo + scene.eevee.taa_render_samples = \ + scene.eevee.taa_samples = scene.grabDoc.samplesAlbedo else: # Cycles - scene.cycles.samples = scene.cycles.preview_samples = scene.grabDoc.samplesCyclesAlbedo + scene.cycles.samples = \ + scene.cycles.preview_samples = scene.grabDoc.samplesCyclesAlbedo render.engine = str(scene.grabDoc.engineAlbedo).upper() - set_color_management_settings('sRGB') + set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_ALBEDO_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_ALBEDO_NAME) # ROUGHNESS -def roughness_setup(self, context: types.Context) -> None: +def roughness_setup(self, context: Context) -> None: scene = context.scene render = scene.render if scene.grabDoc.engineRoughness == 'blender_eevee': - scene.eevee.taa_render_samples = scene.eevee.taa_samples = scene.grabDoc.samplesRoughness + scene.eevee.taa_render_samples = \ + scene.eevee.taa_samples = scene.grabDoc.samplesRoughness else: # Cycles - scene.cycles.samples = scene.cycles.preview_samples = scene.grabDoc.samplesCyclesRoughness + scene.cycles.samples = \ + scene.cycles.preview_samples = scene.grabDoc.samplesCyclesRoughness render.engine = str(scene.grabDoc.engineRoughness).upper() - set_color_management_settings('sRGB') + set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_ROUGHNESS_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_ROUGHNESS_NAME) # METALNESS -def metalness_setup(self, context: types.Context) -> None: +def metalness_setup(self, context: Context) -> None: scene = context.scene render = scene.render if scene.grabDoc.engineMetalness == 'blender_eevee': - scene.eevee.taa_render_samples = scene.eevee.taa_samples = scene.grabDoc.samplesMetalness + scene.eevee.taa_render_samples = \ + scene.eevee.taa_samples = scene.grabDoc.samplesMetalness else: # Cycles - scene.cycles.samples = scene.cycles.preview_samples = scene.grabDoc.samplesMetalness + scene.cycles.samples = \ + scene.cycles.preview_samples = scene.grabDoc.samplesMetalness render.engine = str(scene.grabDoc.engineMetalness).upper() - set_color_management_settings('sRGB') + set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=GlobalVarConst.NG_METALNESS_NAME) + add_ng_to_mat(self, context, setup_type=Global.NG_METALNESS_NAME) # ##### BEGIN GPL LICENSE BLOCK ##### diff --git a/utils/generic.py b/utils/generic.py new file mode 100644 index 0000000..e43ba9d --- /dev/null +++ b/utils/generic.py @@ -0,0 +1,230 @@ +import os +import re +from inspect import getframeinfo, stack + +import bpy +from bpy.types import Context, Operator + +from ..constants import GlobalVariableConstants as Global +from ..constants import ErrorCodeConstants as Error +from ..constants import NAME, VERSION +from ..utils.render import get_rendered_objects + + +class PanelInfo: + bl_category = 'GrabDoc' + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_label = "" + + +class SubPanelInfo: + bl_parent_id = "GRABDOC_PT_view_edit_maps" + bl_options = {'HEADER_LAYOUT_EXPAND', 'DEFAULT_CLOSED'} + + +class UseSelectedOnly(): + @classmethod + def poll(cls, context: Context) -> bool: + return True if len(context.selected_objects) else poll_message_error( + cls, Error.NO_OBJECTS_SELECTED + ) + + +def export_bg_plane(context: Context) -> None: + """Export the grabdoc background plane for external use""" + gd = context.scene.grabDoc + + # Save original selection + savedSelection = context.selected_objects + + # Deselect all objects + bpy.ops.object.select_all(action='DESELECT') + + # Select bg plane, export and deselect bg plane + bpy.data.collections[Global.COLL_NAME].hide_select = False + bpy.data.objects[Global.BG_PLANE_NAME].hide_select = False + bpy.data.objects[Global.BG_PLANE_NAME].select_set(True) + + bpy.ops.export_scene.fbx( + filepath=os.path.join( + bpy.path.abspath(gd.exportPath), + gd.exportName + '_plane.fbx' + ), + use_selection=True + ) + + bpy.data.objects[Global.BG_PLANE_NAME].select_set(False) + + # Refresh original selection + for ob in savedSelection: + ob.select_set(True) + + if not gd.collSelectable: + bpy.data.collections[Global.COLL_NAME].hide_select = False + + +def proper_scene_setup() -> bool: + """Look for grabdoc objects to decide + if the scene is setup correctly""" + object_checks = ( + Global.COLL_NAME in bpy.data.collections, + Global.BG_PLANE_NAME in bpy.context.scene.objects, + Global.TRIM_CAMERA_NAME in bpy.context.scene.objects + ) + return True in object_checks + + +def is_camera_in_3d_view() -> bool: + """Check if we are actively viewing + through the camera in the 3D View""" + return [area.spaces.active.region_3d.view_perspective for area in bpy.context.screen.areas if area.type == 'VIEW_3D'] == ['CAMERA'] + + +# NOTE: Basic DRM is best DRM +def is_pro_version() -> bool: + return "Pro" in NAME + + +def format_bl_label( + name: str=NAME, + bl_version: str=VERSION + ) -> str: + tuples_version_pattern = r'\((\d+), (\d+), (\d+)\)' + match = re.match(tuples_version_pattern, str(bl_version)) + result = '.'.join(match.groups()) if match else None + formatted = f"{name} {result}" + return formatted + + +def get_create_addon_temp_dir( + dir_name: str="temp", + create_dir: bool=True + ) -> tuple[str, str]: + """Creates a temporary files directory + for automatically handled I/O""" + addon_path = os.path.dirname(__file__) + temps_path = os.path.join(addon_path, dir_name) + if create_dir and not os.path.exists(temps_path): + os.mkdir(temps_path) + return (addon_path, temps_path) + + +def get_debug_line_no() -> str: + """Simple method of getting the + line number of a particular error""" + caller = getframeinfo(stack()[2][0]) + file_name = caller.filename.split("\\", -1)[-1] + line_num = caller.lineno + return f"{file_name}:{line_num}" + + +def poll_message_error( + cls: Operator, + error_message: str, + print_err_line: bool=True + ) -> bool: + """Calls the poll_message_set function, for use in operator polls. + This ALWAYS returns a False boolean value if called. + + Parameters + ---------- + error_message : str + Error message to print to the user + print_err_line : bool, optional + Whether the error line will be printed or not, by default True + """ + cls.poll_message_set( + f"{error_message}. ({get_debug_line_no()})'" if print_err_line else f"{error_message}." + ) + return False + + +def get_format_extension() -> str: + """Get the correct file extension based on `imageType` attribute""" + return f".{Global.FORMAT_MATCHED_EXTENSIONS[bpy.context.scene.grabDoc.imageType]}" + + +def bad_setup_check( + self, + context: Context, + active_export: bool, + report_value=False, + report_string="" + ) -> tuple[bool, str]: + """Determine if specific parts of the scene + are set up incorrectly and return a detailed + explanation of things for the user to fix""" + gd = context.scene.grabDoc + + # Run this before other error checks as the + # following error checks contain dependencies + self.rendered_obs = get_rendered_objects(context) + + # Look for Trim Camera (only thing required to render) + if not Global.TRIM_CAMERA_NAME in context.view_layer.objects \ + and not report_value: + report_value = True + report_string = Error.TRIM_CAM_NOT_FOUND + + # Check for no objects in manual collection + if gd.useBakeCollection and not report_value: + if not len(bpy.data.collections[Global.COLL_OB_NAME].objects): + report_value = True + report_string = Error.NO_OBJECTS_BAKE_GROUPS + + if active_export: + # Check for export path + if not os.path.exists(bpy.path.abspath(gd.exportPath)) \ + and not report_value: + report_value = True + report_string = Error.NO_VALID_PATH_SET + + # Check if all bake maps are disabled + bake_maps = ( + gd.exportNormals, + gd.exportCurvature, + gd.exportOcclusion, + gd.exportHeight, + gd.exportMatID, + gd.exportAlpha, + gd.exportAlbedo, + gd.exportRoughness, + gd.exportMetalness + ) + + bake_map_vis = ( + gd.uiVisibilityNormals, + gd.uiVisibilityCurvature, + gd.uiVisibilityOcclusion, + gd.uiVisibilityHeight, + gd.uiVisibilityMatID, + gd.uiVisibilityAlpha, + gd.uiVisibilityAlbedo, + gd.uiVisibilityRoughness, + gd.uiVisibilityMetalness + ) + + if True not in bake_maps or True not in bake_map_vis: + report_value = True + report_string = "No bake maps are turned on." + return (report_value, report_string) + + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### diff --git a/marmoset_utils.py b/utils/marmoset.py similarity index 83% rename from marmoset_utils.py rename to utils/marmoset.py index b2f0429..21b094c 100644 --- a/marmoset_utils.py +++ b/utils/marmoset.py @@ -1,14 +1,20 @@ -import mset, os, json -from .constants import GlobalVariableConstants as GlobalVarConst +import os +import json +import mset -temps_path = os.path.join(os.path.dirname(mset.getPluginPath()), "Temp") +from ..constants import GlobalVariableConstants as Global + + +temps_path = os.path.join(os.path.dirname(mset.getPluginPath()), "_temp") def refresh_scene() -> None: if os.path.exists(os.path.join(temps_path, "marmo_vars.json")): mset.newScene() - with open(os.path.join(temps_path, "marmo_vars.json"), 'r') as openfile: + with open( + os.path.join(temps_path, "marmo_vars.json"), 'r', encoding='utf-8' + ) as openfile: marmo_json = json.load(openfile) ## BAKER SETUP @@ -34,7 +40,9 @@ def refresh_scene() -> None: baker.outputSamples = marmo_json["samples"] # Import the models - baker.importModel(os.path.normpath(os.path.join(temps_path, "GD_temp_model.fbx"))) + baker.importModel( + os.path.normpath(os.path.join(temps_path, "GD_temp_model.fbx")) + ) # Set cage offset mset.findObject('Low').maxOffset = marmo_json["cage_height"] + .01 @@ -49,7 +57,7 @@ def refresh_scene() -> None: # Make a folder for Mat ID materials for mat in mset.getAllMaterials(): - if mat.name.startswith(GlobalVarConst.GD_PREFIX): + if mat.name.startswith(Global.GD_PREFIX): mat.setGroup('Mat ID') ## BAKE MAPS SETUP @@ -113,22 +121,29 @@ def refresh_scene() -> None: mset.findObject('High').visible = False # Scale up the high poly plane - mset.findObject(f'{GlobalVarConst.GD_HIGH_PREFIX} {GlobalVarConst.BG_PLANE_NAME}').scale = [300, 300, 300] + mset.findObject( + f'{Global.GD_HIGH_PREFIX} {Global.BG_PLANE_NAME}' + ).scale = [300, 300, 300] findDefault = mset.findMaterial("Default") # Material preview if marmo_json["export_normal"]: - findDefault.getSubroutine('surface').setField('Normal Map', marmo_json["file_path"][:-4] + '_' + marmo_json['suffix_normal'] + '.' + marmo_json['file_ext']) + findDefault.getSubroutine('surface').setField( + 'Normal Map', marmo_json["file_path"][:-4] + '_' + marmo_json['suffix_normal'] + '.' + marmo_json['file_ext'] + ) if marmo_json["export_occlusion"]: findDefault.setSubroutine('occlusion', 'Occlusion') - findDefault.getSubroutine('occlusion').setField('Occlusion Map', marmo_json["file_path"][:-4] + '_' + marmo_json['suffix_occlusion'] + '.' + marmo_json['file_ext']) + findDefault.getSubroutine('occlusion').setField( + 'Occlusion Map', marmo_json["file_path"][:-4] + '_' + marmo_json['suffix_occlusion'] + '.' + marmo_json['file_ext'] + ) # Rename bake material findDefault.name = 'Bake Material' - # Remove the json file to signal the no rebakes need to take place until a new one is made + # Remove the json file to signal the no rebakes + # need to take place until a new one is made os.remove(os.path.join(temps_path, "marmo_vars.json")) diff --git a/utils/node.py b/utils/node.py new file mode 100644 index 0000000..8e3f33f --- /dev/null +++ b/utils/node.py @@ -0,0 +1,655 @@ +import bpy +from bpy.types import ( + Object, + ShaderNodeGroup, + NodeSocket, + Context, + Material +) + +from ..constants import GlobalVariableConstants as Global +from ..constants import ErrorCodeConstants as Error + + +def ng_setup() -> None: + """Initial setup of all node groups when setting up a file""" + gd = bpy.context.scene.grabDoc + + # Create a dummy material output node to harvest potential inputs + ng_dummy_output = bpy.data.node_groups.new( + 'Material Output', + 'ShaderNodeTree' + ) + output = ng_dummy_output.nodes.new( + 'ShaderNodeOutputMaterial' + ) + + collected_inputs = {} + for node_input in output.inputs: + # NOTE: I have no idea why this secret input exists + if node_input.name == 'Thickness': + continue + + collected_inputs[node_input.name] = \ + f'NodeSocket{node_input.type.capitalize()}' + + # NORMALS + if not Global.NG_NORMAL_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new( + Global.NG_NORMAL_NAME, 'ShaderNodeTree' + ) + tree.use_fake_user = True + + # Create group inputs/outputs + tree.interface.new_socket(name="Input", in_out='INPUT') + + tree.interface.new_panel(name="My Panel") + + group_outputs = tree.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + group_inputs = tree.nodes.new('NodeGroupInput') + group_inputs.name = "Group Input" + group_inputs.location = (-1400,100) + tree.interface.new_socket( + name="Output", socket_type='NodeSocketShader', in_out='OUTPUT' + ) + for key, value in collected_inputs.items(): + tree.interface.new_socket(name=f'Saved {key}', socket_type=value) + alpha_input = tree.interface.new_socket( + name='Alpha', + socket_type='NodeSocketFloat' + ) + alpha_input.default_value = 1 + tree.interface.new_socket(name='Normal', socket_type='NodeSocketVector') + + # Create group nodes + bevel = tree.nodes.new('ShaderNodeBevel') + bevel.name = "Bevel" + bevel.inputs[0].default_value = 0 + bevel.location = (-1000,0) + + bevel_node_2 = tree.nodes.new('ShaderNodeBevel') + bevel_node_2.name = "Bevel.001" + bevel_node_2.location = (-1000,-200) + bevel_node_2.inputs[0].default_value = 0 + + vec_transform = tree.nodes.new('ShaderNodeVectorTransform') + vec_transform.name = "Vector Transform" + vec_transform.vector_type = 'NORMAL' + vec_transform.convert_to = 'CAMERA' + vec_transform.location = (-800,0) + + vec_mult = tree.nodes.new('ShaderNodeVectorMath') + vec_mult.name = "Vector Math" + vec_mult.operation = 'MULTIPLY' + vec_mult.inputs[1].default_value[0] = .5 + vec_mult.inputs[1].default_value[1] = -.5 if gd.flipYNormals else .5 + vec_mult.inputs[1].default_value[2] = -.5 + vec_mult.location = (-600,0) + + vec_add = tree.nodes.new('ShaderNodeVectorMath') + vec_add.name = "Vector Math.001" + vec_add.inputs[1].default_value[0] = \ + vec_add.inputs[1].default_value[1] = \ + vec_add.inputs[1].default_value[2] = \ + 0.5 + vec_add.location = (-400,0) + + invert = tree.nodes.new('ShaderNodeInvert') + invert.name = "Invert" + invert.location = (-1000,200) + + subtract = tree.nodes.new('ShaderNodeMixRGB') + subtract.blend_type = 'SUBTRACT' + subtract.name = "Subtract" + subtract.inputs[0].default_value = 1 + subtract.inputs[1].default_value = (1, 1, 1, 1) + subtract.location = (-800,300) + + transp_shader = tree.nodes.new('ShaderNodeBsdfTransparent') + transp_shader.name = "Transparent BSDF" + transp_shader.location = (-400,200) + + mix_shader = tree.nodes.new('ShaderNodeMixShader') + mix_shader.name = "Mix Shader" + mix_shader.location = (-200,300) + + # Link nodes + link = tree.links + + # Path 1 + link.new(bevel.inputs["Normal"], group_inputs.outputs["Normal"]) + link.new(vec_transform.inputs["Vector"], bevel_node_2.outputs["Normal"]) + link.new(vec_mult.inputs["Vector"], vec_transform.outputs["Vector"]) + link.new(vec_add.inputs["Vector"], vec_mult.outputs["Vector"]) + link.new(group_outputs.inputs["Output"], vec_add.outputs["Vector"]) + + # Path 2 + link.new(invert.inputs['Color'], group_inputs.outputs['Alpha']) + link.new(subtract.inputs['Color2'], invert.outputs['Color']) + link.new(mix_shader.inputs['Fac'], subtract.outputs['Color']) + link.new(mix_shader.inputs[1], transp_shader.outputs['BSDF']) + link.new(mix_shader.inputs[2], vec_add.outputs['Vector']) + + # AMBIENT OCCLUSION + if not Global.NG_AO_NAME in bpy.data.node_groups: + ng_ao = bpy.data.node_groups.new(Global.NG_AO_NAME, 'ShaderNodeTree') + ng_ao.use_fake_user = True + + # Create group inputs/outputs + group_outputs = ng_ao.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + ng_ao.outputs.new('NodeSocketShader','Output') + for key, value in collected_inputs.items(): + ng_ao.inputs.new(value, f'Saved {key}') + + # Create group nodes + ao = ng_ao.nodes.new('ShaderNodeAmbientOcclusion') + ao.name = "Ambient Occlusion" + ao.samples = 32 + ao.location = (-600,0) + + gamma = ng_ao.nodes.new('ShaderNodeGamma') + gamma.name = "Gamma" + gamma.inputs[1].default_value = gd.gammaOcclusion + gamma.location = (-400,0) + + emission = ng_ao.nodes.new('ShaderNodeEmission') + emission.name = "Emission" + emission.location = (-200,0) + + # Link nodes + link = ng_ao.links + link.new(gamma.inputs["Color"], ao.outputs["Color"]) + link.new(emission.inputs["Color"], gamma.outputs["Color"]) + link.new(group_outputs.inputs["Output"], emission.outputs["Emission"]) + + # HEIGHT + if not Global.NG_HEIGHT_NAME in bpy.data.node_groups: + ng_height = bpy.data.node_groups.new( + Global.NG_HEIGHT_NAME, + 'ShaderNodeTree' + ) + ng_height.use_fake_user = True + + # Create group inputs/outputs + group_outputs = ng_height.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + ng_height.outputs.new('NodeSocketShader','Output') + for key, value in collected_inputs.items(): + ng_height.inputs.new(value, f'Saved {key}') + + # Create group nodes + camera = ng_height.nodes.new('ShaderNodeCameraData') + camera.name = "Camera Data" + camera.location = (-800,0) + + # NOTE: Map Range updates handled on map preview + map_range = ng_height.nodes.new('ShaderNodeMapRange') + map_range.name = "Map Range" + map_range.location = (-600,0) + + ramp = ng_height.nodes.new('ShaderNodeValToRGB') + ramp.name = "ColorRamp" + ramp.color_ramp.elements[0].color = (1, 1, 1, 1) + ramp.color_ramp.elements[1].color = (0, 0, 0, 1) + ramp.location = (-400,0) + + # Link nodes + link = ng_height.links + link.new(map_range.inputs["Value"], camera.outputs["View Z Depth"]) + link.new(ramp.inputs["Fac"], map_range.outputs["Result"]) + link.new(group_outputs.inputs["Output"], ramp.outputs["Color"]) + + # ALPHA + if not Global.NG_ALPHA_NAME in bpy.data.node_groups: + ng_alpha = bpy.data.node_groups.new( + Global.NG_ALPHA_NAME, + 'ShaderNodeTree' + ) + ng_alpha.use_fake_user = True + + # Create group input/outputs + group_outputs = ng_alpha.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + ng_alpha.outputs.new('NodeSocketShader','Output') + for key, value in collected_inputs.items(): + ng_alpha.inputs.new(value, f'Saved {key}') + + # Create group nodes + camera = ng_alpha.nodes.new('ShaderNodeCameraData') + camera.name = "Camera Data" + camera.location = (-800,0) + + gd_camera_ob_z = \ + bpy.data.objects.get(Global.TRIM_CAMERA_NAME).location[2] + + map_range = ng_alpha.nodes.new('ShaderNodeMapRange') + map_range.name = "Map Range" + map_range.location = (-600,0) + map_range.inputs[1].default_value = gd_camera_ob_z - .00001 + map_range.inputs[2].default_value = gd_camera_ob_z + + invert = ng_alpha.nodes.new('ShaderNodeInvert') + invert.name = "Invert" + invert.location = (-400,0) + + emission = ng_alpha.nodes.new('ShaderNodeEmission') + emission.name = "Emission" + emission.location = (-200,0) + + # Link nodes + link = ng_alpha.links + link.new(map_range.inputs["Value"], camera.outputs["View Z Depth"]) + link.new(invert.inputs["Color"], map_range.outputs["Result"]) + link.new(emission.inputs["Color"], invert.outputs["Color"]) + link.new(group_outputs.inputs["Output"], emission.outputs["Emission"]) + + # ALBEDO + if not Global.NG_ALBEDO_NAME in bpy.data.node_groups: + ng_albedo = \ + bpy.data.node_groups.new( + Global.NG_ALBEDO_NAME, + 'ShaderNodeTree' + ) + ng_albedo.use_fake_user = True + + # Create group inputs/outputs + group_outputs = ng_albedo.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + group_inputs = ng_albedo.nodes.new('NodeGroupInput') + group_inputs.name = "Group Input" + group_inputs.location = (-400,0) + ng_albedo.outputs.new('NodeSocketShader','Output') + ng_albedo.inputs.new('NodeSocketColor', 'Color Input') + for key, value in collected_inputs.items(): + ng_albedo.inputs.new(value, f'Saved {key}') + + emission = ng_albedo.nodes.new('ShaderNodeEmission') + emission.name = "Emission" + emission.location = (-200,0) + + # Link nodes + link = ng_albedo.links + link.new(emission.inputs["Color"], group_inputs.outputs["Color Input"]) + link.new(group_outputs.inputs["Output"], emission.outputs["Emission"]) + + # ROUGHNESS + if not Global.NG_ROUGHNESS_NAME in bpy.data.node_groups: + ng_roughness = bpy.data.node_groups.new( + Global.NG_ROUGHNESS_NAME, + 'ShaderNodeTree' + ) + ng_roughness.use_fake_user = True + + # Create group inputs/outputs + group_outputs = ng_roughness.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + group_inputs = ng_roughness.nodes.new('NodeGroupInput') + group_inputs.name = "Group Input" + group_inputs.location = (-600,0) + ng_roughness.outputs.new('NodeSocketShader','Output') + ng_roughness.inputs.new('NodeSocketFloat', 'Roughness Input') + for key, value in collected_inputs.items(): + ng_roughness.inputs.new(value, f'Saved {key}') + + invert = ng_roughness.nodes.new('ShaderNodeInvert') + invert.location = (-400,0) + invert.inputs[0].default_value = 0 + + emission = ng_roughness.nodes.new('ShaderNodeEmission') + emission.name = "Emission" + emission.location = (-200,0) + + # Link nodes + link = ng_roughness.links + link.new( + invert.inputs["Color"], + group_inputs.outputs["Roughness Input"] + ) + link.new( + emission.inputs["Color"], + invert.outputs["Color"] + ) + link.new( + group_outputs.inputs["Output"], + emission.outputs["Emission"] + ) + + # METALNESS + if not Global.NG_METALNESS_NAME in bpy.data.node_groups: + ng_roughness = \ + bpy.data.node_groups.new( + Global.NG_METALNESS_NAME, + 'ShaderNodeTree' + ) + ng_roughness.use_fake_user = True + + # Create group inputs/outputs + group_outputs = ng_roughness.nodes.new('NodeGroupOutput') + group_outputs.name = "Group Output" + group_inputs = ng_roughness.nodes.new('NodeGroupInput') + group_inputs.name = "Group Input" + group_inputs.location = (-400,0) + ng_roughness.outputs.new('NodeSocketShader','Output') + ng_roughness.inputs.new('NodeSocketFloat', 'Metalness Input') + for key, value in collected_inputs.items(): + ng_roughness.inputs.new(value, f'Saved {key}') + + emission = ng_roughness.nodes.new('ShaderNodeEmission') + emission.name = "Emission" + emission.location = (-200,0) + + # Link nodes + link = ng_roughness.links + link.new( + emission.inputs["Color"], + group_inputs.outputs["Metalness Input"] + ) + link.new( + group_outputs.inputs["Output"], + emission.outputs["Emission"] + ) + + +def create_apply_ng_mat(ob: Object) -> None: + """Create & apply a material to objects without active materials""" + mat_name = Global.GD_MATERIAL_NAME + + # Reuse GrabDoc created material if it already exists + if mat_name in bpy.data.materials: + mat = bpy.data.materials[mat_name] + else: + mat = bpy.data.materials.new(name = mat_name) + mat.use_nodes = True + + # Apply the material to the appropriate slot + # + # Search through all material slots, if any + # slots have no name that means they have no + # material & therefore should have one added. + # While this does run every time this is called + # it should only really need to be used once per + # in add-on use. + # + # We do not want to remove empty material slots + # as they can be used for masking off materials. + for slot in ob.material_slots: + if slot.name == '': + ob.material_slots[slot.name].material = mat + if not ob.active_material or ob.active_material.name == '': + ob.active_material = mat + + +def bsdf_link_factory( + input_name: list, + node_group: ShaderNodeGroup, + original_input: NodeSocket, + mat_slot: Material + ) -> bool: + """Add node group to all materials, save original + links, and link the node group to material output""" + node_found = False + for link in original_input.links: + node_found = True + + mat_slot.node_tree.links.new( + node_group.inputs[input_name], + link.from_node.outputs[link.from_socket.name] + ) + break + else: + try: + node_group.inputs[input_name].default_value = \ + original_input.default_value + except TypeError: + if isinstance(original_input.default_value, float): + node_group.inputs[input_name].default_value = \ + int(original_input.default_value) + else: + node_group.inputs[input_name].default_value = \ + float(original_input.default_value) + return node_found + + +def add_ng_to_mat(self, context: Context, setup_type: str) -> None: + """Add corresponding node groups to all materials/objects""" + for ob in context.view_layer.objects: + if ( + ob.name not in self.rendered_obs \ + and ob.name == Global.ORIENT_GUIDE_NAME + ): + continue + + # If no material slots found or empty mat + # slots found, assign a material to it + if not ob.material_slots or '' in ob.material_slots: + create_apply_ng_mat(ob) + + # Cycle through all material slots + for slot in ob.material_slots: + mat_slot = bpy.data.materials.get(slot.name) + mat_slot.use_nodes = True + + nodes = mat_slot.node_tree.nodes + if setup_type in nodes: + continue + + # Get materials Output Material node(s) + output_nodes = { + mat for mat in nodes if mat.type == 'OUTPUT_MATERIAL' + } + if not output_nodes: + output_nodes.append( + nodes.new('ShaderNodeOutputMaterial') + ) + + node_group = bpy.data.node_groups.get(setup_type) + for output in output_nodes: + # Add node group to material + GD_node_group = nodes.new('ShaderNodeGroup') + GD_node_group.node_tree = node_group + GD_node_group.location = ( + output.location[0], + output.location[1] - 160 + ) + GD_node_group.name = node_group.name + GD_node_group.hide = True + + # Add note next to node group explaining basic functionality + GD_text = bpy.data.texts.get('_grabdoc_ng_warning') + if GD_text is None: + GD_text = bpy.data.texts.new(name='_grabdoc_ng_warning') + + GD_text.clear() + GD_text.write(Global.NG_NODE_WARNING) + + GD_frame = nodes.new('NodeFrame') + GD_frame.location = ( + output.location[0], + output.location[1] - 195 + ) + GD_frame.name = node_group.name + GD_frame.text = GD_text + GD_frame.width = 1000 + GD_frame.height = 150 + + # Handle node linking + for node_input in output.inputs: + for link in node_input.links: + original = nodes.get(link.from_node.name) + + # Link original connections to the Node Group + # TODO: can be more modular + connections_to_make = \ + ('Surface', 'Volume', 'Displacement') + if node_input.name in connections_to_make: + for connection_name in connections_to_make: + if node_input.name != connection_name: + continue + mat_slot.node_tree.links.new( + GD_node_group.inputs[ + f"Saved {connection_name}" + ], + original.outputs[link.from_socket.name] + ) + + # Links for maps that feed information + # from the Principled BSDF + if setup_type not in ( + Global.NG_ALBEDO_NAME, + Global.NG_ROUGHNESS_NAME, + Global.NG_METALNESS_NAME, + Global.NG_NORMAL_NAME + ) and original.type != 'BSDF_PRINCIPLED': + continue + + node_found = False + for original_input in original.inputs: + if ( + setup_type == Global.NG_ALBEDO_NAME \ + and original_input.name == 'Base Color' + ): + node_found = bsdf_link_factory( + input_name='Color Input', + node_group=GD_node_group, + original_input=original_input, + mat_slot=mat_slot + ) + elif ( + setup_type == Global.NG_ROUGHNESS_NAME \ + and original_input.name == 'Roughness' + ): + node_found = bsdf_link_factory( + input_name='Roughness Input', + node_group=GD_node_group, + original_input=original_input, + mat_slot=mat_slot + ) + elif ( + setup_type == Global.NG_METALNESS_NAME \ + and original_input.name == 'Metallic' + ): + node_found = bsdf_link_factory( + input_name='Metalness Input', + node_group=GD_node_group, + original_input=original_input, + mat_slot=mat_slot + ) + elif ( + setup_type == Global.NG_NORMAL_NAME \ + and original_input.name in ('Normal', 'Alpha') + ): + node_found = bsdf_link_factory( + input_name=original_input.name, + node_group=GD_node_group, + original_input=original_input, + mat_slot=mat_slot + ) + + # Does not work if Map Preview + # Mode is entered and *then* + # Texture Normals are enabled + if ( + original_input.name == 'Alpha' \ + and context.scene.grabDoc.useTextureNormals\ + and mat_slot.blend_method == 'OPAQUE' \ + and len(original_input.links) + ): + mat_slot.blend_method = 'CLIP' + elif node_found: + break + + if ( + not node_found \ + and setup_type != Global.NG_NORMAL_NAME \ + and mat_slot.name != Global.GD_MATERIAL_NAME + ): + self.report( + {'WARNING'}, + Error.MAT_SLOTS_WITHOUT_LINKS + ) + + for link in output.inputs['Volume'].links: + mat_slot.node_tree.links.remove(link) + for link in output.inputs['Displacement'].links: + mat_slot.node_tree.links.remove(link) + + # Link Node Group to the output + mat_slot.node_tree.links.new( + output.inputs["Surface"], + GD_node_group.outputs["Output"] + ) + + +def cleanup_ng_from_mat(setup_type: str) -> None: + """Remove node group & return original links if they exist""" + for mat in bpy.data.materials: + mat.use_nodes = True + + # If there is a GrabDoc created material, remove it + if mat.name == Global.GD_MATERIAL_NAME: + bpy.data.materials.remove(mat) + continue + elif setup_type not in mat.node_tree.nodes: + continue + + # If a material has a GrabDoc created Node Group, remove it + GD_node_groups = [ + mat for mat in mat.node_tree.nodes if mat.name.startswith( + setup_type + ) + ] + for GD_node_group in GD_node_groups: + output = None + for output in GD_node_group.outputs: + for link in output.links: + if link.to_node.type == 'OUTPUT_MATERIAL': + output = link.to_node + break + if output is not None: + break + + if output is None: + mat.node_tree.nodes.remove(GD_node_group) + continue + + for node_input in GD_node_group.inputs: + for link in node_input.links: + original_node_connection = \ + mat.node_tree.nodes.get(link.from_node.name) + original_node_socket = link.from_socket.name + + # TODO: can be more modular + connections_to_make = \ + ('Surface', 'Volume', 'Displacement') + if node_input.name.split(' ')[-1] in connections_to_make: + for connection_name in connections_to_make: + if node_input.name == f'Saved {connection_name}': + mat.node_tree.links.new( + output.inputs[connection_name], + original_node_connection.outputs[ + original_node_socket + ] + ) + + mat.node_tree.nodes.remove(GD_node_group) + + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### diff --git a/utils/render.py b/utils/render.py new file mode 100644 index 0000000..f6d593b --- /dev/null +++ b/utils/render.py @@ -0,0 +1,145 @@ + +from ast import List +from mathutils import Vector + +import bpy +from bpy.types import Context, Object + +from ..constants import GlobalVariableConstants as Global + + +def is_valid_grabdoc_object( + ob: Object, + has_prefix: bool=False, + render_visible: bool=True, + invalid_type: bool=True, + is_gd_ob: bool=True + ) -> bool: + """Basic validation checks to detect if an object + will work well within the GrabDoc environment""" + # Default off + if has_prefix and not ob.name.startswith(Global.GD_PREFIX): + return False + + # Default on + if render_visible and not ob.hide_render: + return False + if invalid_type and ob.type not in Global.INVALID_RENDER_TYPES: + return False + if is_gd_ob and not ob.is_gd_object: + return False + return True + + +def in_viewing_frustrum(vector: Vector) -> bool: + """Decide whether a given object is + within the cameras viewing frustrum""" + bg_plane = bpy.data.objects[Global.BG_PLANE_NAME] + viewing_frustrum = ( + Vector + ( + ( + bg_plane.dimensions.x * -1.25 + bg_plane.location[0], + bg_plane.dimensions.y * -1.25 + bg_plane.location[1], + -100 + ) + ), + Vector( + ( + bg_plane.dimensions.x * -1.25 + bg_plane.location[0], + bg_plane.dimensions.y * -1.25 + bg_plane.location[1], + -100 + ) + ) + ) + for i in range(0, 3): + if ( + vector[i] < viewing_frustrum[0][i] \ + and vector[i] < viewing_frustrum[1][i] \ + or vector[i] > viewing_frustrum[0][i] \ + and vector[i] > viewing_frustrum[1][i] + ): + return False + return True + + +def get_rendered_objects(context: Context) -> set | None: + """Generate a list of all objects that will be rendered + based on its origin position in world space""" + rendered_obs = {Global.BG_PLANE_NAME} + if context.scene.grabDoc.useBakeCollection: + for coll in bpy.data.collections: + if coll.is_gd_collection is False: + continue + rendered_obs.update( + [ob for ob in coll.all_objects if is_valid_grabdoc_object(ob)] + ) + # TODO: Old method, maybe time it? + #for ob in coll.all_objects: + # if is_valid_grabdoc_object(ob): + # rendered_obs.add(ob.name) + return rendered_obs + + for ob in context.view_layer.objects: + if is_valid_grabdoc_object(ob): + local_bbox_center = .125 * sum( + (Vector(b) for b in ob.bound_box), Vector() + ) + global_bbox_center = ob.matrix_world @ local_bbox_center + + if in_viewing_frustrum(global_bbox_center): + rendered_obs.add(ob.name) + return rendered_obs + + +def set_guide_height(objects: List[Object]) -> None: + """Set guide height maximum property value + based on a given list of objects""" + tallest_vert = find_tallest_object(objects) + bg_plane = bpy.data.objects.get(Global.BG_PLANE_NAME) + bpy.context.scene.grabDoc.guideHeight = \ + tallest_vert - bg_plane.location[2] + + +def find_tallest_object(objects: List[Object]) -> None: + """Find the tallest points in the viewlayer by looping + through objects to find the highest vertex on the Z axis""" + depsgraph = bpy.context.evaluated_depsgraph_get() + tallest_verts = [] + objects = [ob for ob in objects if ob.name.startswith(Global.GD_PREFIX)] + for ob in objects: + ob_eval = ob.evaluated_get(depsgraph) + mesh_eval = ob_eval.to_mesh() + + global_vert_co = [ + ob_eval.matrix_world @ v.co for v in mesh_eval.vertices + ] + + # NOTE: Find highest Z-value amongst + # object's vertices and append to list + if len(global_vert_co): + max_z_co = max(co.z for co in global_vert_co) + tallest_verts.append(max_z_co) + ob_eval.to_mesh_clear() + if not tallest_verts: + return None + return max(tallest_verts) + + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### diff --git a/scene_setup_utils.py b/utils/scene.py similarity index 57% rename from scene_setup_utils.py rename to utils/scene.py index 3adeb78..a57d78c 100644 --- a/scene_setup_utils.py +++ b/utils/scene.py @@ -1,23 +1,28 @@ +import bpy +import bmesh +from bpy.types import Context -import bpy, bmesh, bpy.types as types -from .node_group_utils import ng_setup -from .generic_utils import is_camera_in_3d_view -from .constants import GlobalVariableConstants as GlobalVarConst +from ..constants import GlobalVariableConstants as Global +from ..utils.generic import is_camera_in_3d_view +from ..utils.node import ng_setup -def remove_setup(context: types.Context, hard_reset: bool=True) -> None | list: - """Completely removes every element of GrabDoc from the scene, not including images reimported after bakes +def remove_setup(context: Context, hard_reset: bool=True) -> None | list: + """Completely removes every element of GrabDoc from + the scene, not including images reimported after bakes -hard_reset: When refreshing a scene we may want to keep certain data-blocks that the user can manipulates""" + hard_reset: When refreshing a scene we may want to keep + certain data-blocks that the user can manipulates""" # COLLECTIONS - # Move objects contained inside the bake group collection to the root collection level and delete the collection + # Move objects contained inside the bake group collection + # to the root collection level and delete the collection saved_bake_group_obs = [] - bake_group_coll = bpy.data.collections.get(GlobalVarConst.COLL_OB_NAME) + bake_group_coll = bpy.data.collections.get(Global.COLL_OB_NAME) if bake_group_coll is not None: for ob in bake_group_coll.all_objects: # Move object to the master collection - if hard_reset or not context.scene.grabDoc.onlyRenderColl: + if hard_reset or not context.scene.grabDoc.useBakeCollection: context.scene.collection.objects.link(ob) else: saved_bake_group_obs.append(ob) @@ -27,11 +32,17 @@ def remove_setup(context: types.Context, hard_reset: bool=True) -> None | list: bpy.data.collections.remove(bake_group_coll) - # Move objects accidentally placed in the GD collection to the master collection and then remove the collection - gd_coll = bpy.data.collections.get(GlobalVarConst.COLL_NAME) + # Move objects accidentally placed in the GD collection to + # the master collection and then remove the collection + gd_coll = bpy.data.collections.get(Global.COLL_NAME) if gd_coll is not None: for ob in gd_coll.all_objects: - if ob.name not in (GlobalVarConst.BG_PLANE_NAME, GlobalVarConst.ORIENT_GUIDE_NAME, GlobalVarConst.HEIGHT_GUIDE_NAME, GlobalVarConst.TRIM_CAMERA_NAME): + if ob.name not in ( + Global.BG_PLANE_NAME, + Global.ORIENT_GUIDE_NAME, + Global.HEIGHT_GUIDE_NAME, + Global.TRIM_CAMERA_NAME + ): # Move object to the master collection context.scene.collection.objects.link(ob) @@ -43,18 +54,22 @@ def remove_setup(context: types.Context, hard_reset: bool=True) -> None | list: # HARD RESET - a simpler method for clearing all gd related object if hard_reset: - for ob_name in (GlobalVarConst.BG_PLANE_NAME, GlobalVarConst.ORIENT_GUIDE_NAME, GlobalVarConst.HEIGHT_GUIDE_NAME): + for ob_name in ( + Global.BG_PLANE_NAME, + Global.ORIENT_GUIDE_NAME, + Global.HEIGHT_GUIDE_NAME + ): if ob_name in bpy.data.objects: bpy.data.meshes.remove(bpy.data.meshes[ob_name]) - if GlobalVarConst.TRIM_CAMERA_NAME in bpy.data.objects: - bpy.data.cameras.remove(bpy.data.cameras[GlobalVarConst.TRIM_CAMERA_NAME]) + if Global.TRIM_CAMERA_NAME in bpy.data.objects: + bpy.data.cameras.remove(bpy.data.cameras[Global.TRIM_CAMERA_NAME]) - if GlobalVarConst.REFERENCE_NAME in bpy.data.materials: - bpy.data.materials.remove(bpy.data.materials[GlobalVarConst.REFERENCE_NAME]) + if Global.REFERENCE_NAME in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials[Global.REFERENCE_NAME]) for ngroup in bpy.data.node_groups: - if ngroup.name.startswith(GlobalVarConst.GD_PREFIX): + if ngroup.name.startswith(Global.GD_PREFIX): bpy.data.node_groups.remove(ngroup) return None @@ -64,8 +79,8 @@ def remove_setup(context: types.Context, hard_reset: bool=True) -> None | list: # Bg plane saved_mat = None saved_plane_loc = saved_plane_rot = (0, 0, 0) - if GlobalVarConst.BG_PLANE_NAME in bpy.data.objects: - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + if Global.BG_PLANE_NAME in bpy.data.objects: + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] # Save material/reference & transforms saved_mat = plane_ob.active_material @@ -75,25 +90,27 @@ def remove_setup(context: types.Context, hard_reset: bool=True) -> None | list: bpy.data.meshes.remove(plane_ob.data) # Camera - # Forcibly exit the camera before deleting it so the original users camera position is retained + # Forcibly exit the camera before deleting it so + # the original users camera position is retained reposition_cam = False if is_camera_in_3d_view(): reposition_cam = True bpy.ops.view3d.view_camera() - if GlobalVarConst.TRIM_CAMERA_NAME in bpy.data.cameras: - bpy.data.cameras.remove(bpy.data.cameras[GlobalVarConst.TRIM_CAMERA_NAME]) - if GlobalVarConst.HEIGHT_GUIDE_NAME in bpy.data.meshes: - bpy.data.meshes.remove(bpy.data.meshes[GlobalVarConst.HEIGHT_GUIDE_NAME]) - if GlobalVarConst.ORIENT_GUIDE_NAME in bpy.data.meshes: - bpy.data.meshes.remove(bpy.data.meshes[GlobalVarConst.ORIENT_GUIDE_NAME]) + if Global.TRIM_CAMERA_NAME in bpy.data.cameras: + bpy.data.cameras.remove(bpy.data.cameras[Global.TRIM_CAMERA_NAME]) + if Global.HEIGHT_GUIDE_NAME in bpy.data.meshes: + bpy.data.meshes.remove(bpy.data.meshes[Global.HEIGHT_GUIDE_NAME]) + if Global.ORIENT_GUIDE_NAME in bpy.data.meshes: + bpy.data.meshes.remove(bpy.data.meshes[Global.ORIENT_GUIDE_NAME]) return saved_plane_loc, saved_plane_rot, saved_mat, reposition_cam, saved_bake_group_obs -def scene_setup(self, context: types.Context) -> None: # Needs self for update functions to register? +# NOTE:: Needs self for update functions to register? +def scene_setup(_self, context: Context) -> None: """Generate/setup all relevant GrabDoc object, collections, node groups and scene settings""" - grabDoc = context.scene.grabDoc + gd = context.scene.grabDoc view_layer = context.view_layer # PRELIMINARY @@ -101,7 +118,7 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f saved_active_collection = view_layer.active_layer_collection saved_selected_obs = view_layer.objects.selected.keys() - gd_coll = bpy.data.collections.get(GlobalVarConst.COLL_NAME) + gd_coll = bpy.data.collections.get(Global.COLL_NAME) if context.object: active_ob = context.object @@ -121,17 +138,19 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f ob.select_set(False) # Remove all related GrabDoc datablocks, keeping some necessary values - saved_plane_loc, saved_plane_rot, saved_mat, reposition_cam, saved_bake_group_obs = remove_setup(context, hard_reset=False) + saved_plane_loc, saved_plane_rot, saved_mat, reposition_cam, saved_bake_group_obs = \ + remove_setup(context, hard_reset=False) - # Set scene resolution (Trying to avoid destructively changing values, but these two need to be changed) - context.scene.render.resolution_x = grabDoc.exportResX - context.scene.render.resolution_y = grabDoc.exportResY + # Set scene resolution (Trying to avoid destructively + # changing values, but these two need to be changed) + context.scene.render.resolution_x = gd.exportResX + context.scene.render.resolution_y = gd.exportResY # COLLECTIONS # Create a bake group collection if requested - if grabDoc.onlyRenderColl: - bake_group_coll = bpy.data.collections.new(name=GlobalVarConst.COLL_OB_NAME) + if gd.useBakeCollection: + bake_group_coll = bpy.data.collections.new(name=Global.COLL_OB_NAME) bake_group_coll.is_gd_collection = True context.scene.collection.children.link(bake_group_coll) @@ -142,19 +161,20 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f bake_group_coll.objects.link(ob) # Create main GrabDoc collection - gd_coll = bpy.data.collections.new(name=GlobalVarConst.COLL_NAME) + gd_coll = bpy.data.collections.new(name=Global.COLL_NAME) gd_coll.is_gd_collection = True context.scene.collection.children.link(gd_coll) view_layer.active_layer_collection = view_layer.layer_collection.children[-1] # Make the GrabDoc collection the active collection - view_layer.active_layer_collection = view_layer.layer_collection.children[gd_coll.name] + view_layer.active_layer_collection = \ + view_layer.layer_collection.children[gd_coll.name] # BG PLANE bpy.ops.mesh.primitive_plane_add( - size=grabDoc.scalingSet, + size=gd.scalingSet, calc_uvs=True, align='WORLD', location=saved_plane_loc, @@ -162,17 +182,17 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f ) # Rename newly made BG Plane & set a reference to it - context.object.name = context.object.data.name = GlobalVarConst.BG_PLANE_NAME - plane_ob = bpy.data.objects[GlobalVarConst.BG_PLANE_NAME] + context.object.name = context.object.data.name = Global.BG_PLANE_NAME + plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] # Prepare proper plane scaling - if grabDoc.exportResX != grabDoc.exportResY: - if grabDoc.exportResX > grabDoc.exportResY: - div_factor = grabDoc.exportResX / grabDoc.exportResY + if gd.exportResX != gd.exportResY: + if gd.exportResX > gd.exportResY: + div_factor = gd.exportResX / gd.exportResY plane_ob.scale[1] /= div_factor else: - div_factor = grabDoc.exportResY / grabDoc.exportResX + div_factor = gd.exportResY / gd.exportResX plane_ob.scale[0] /= div_factor @@ -183,31 +203,35 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f plane_ob.show_wire = True plane_ob.lock_scale[0] = plane_ob.lock_scale[1] = plane_ob.lock_scale[2] = True - # Add reference to the plane if one has been added, else find and remove any existing reference materials - if grabDoc.refSelection and not grabDoc.modalState: + # Add reference to the plane if one has been added + # else find and remove any existing reference materials + if gd.refSelection and not gd.modalState: for mat in bpy.data.materials: - if mat.name == GlobalVarConst.REFERENCE_NAME: - mat.node_tree.nodes.get('Image Texture').image = grabDoc.refSelection + if mat.name == Global.REFERENCE_NAME: + mat.node_tree.nodes.get('Image Texture').image = gd.refSelection break else: # Create a new material & turn on node use - mat = bpy.data.materials.new(GlobalVarConst.REFERENCE_NAME) + mat = bpy.data.materials.new(Global.REFERENCE_NAME) mat.use_nodes = True # Get / load nodes - output_node = mat.node_tree.nodes.get('Material Output') - output_node.location = (0,0) + output = mat.node_tree.nodes.get('Material Output') + output.location = (0,0) mat.node_tree.nodes.remove(mat.node_tree.nodes.get('Principled BSDF')) - image_node = mat.node_tree.nodes.new('ShaderNodeTexImage') - image_node.image = grabDoc.refSelection - image_node.location = (-300,0) + image = mat.node_tree.nodes.new('ShaderNodeTexImage') + image.image = gd.refSelection + image.location = (-300,0) # Link materials - mat.node_tree.links.new(output_node.inputs["Surface"], image_node.outputs["Color"]) + mat.node_tree.links.new( + output.inputs["Surface"], + image.outputs["Color"] + ) - plane_ob.active_material = bpy.data.materials[GlobalVarConst.REFERENCE_NAME] + plane_ob.active_material = bpy.data.materials[Global.REFERENCE_NAME] for area in context.screen.areas: if area.type == 'VIEW_3D': @@ -218,18 +242,24 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f if saved_mat is not None: plane_ob.active_material = saved_mat - # TODO This is the exception to splitting the setup and remove operations into separate functions, maybe can fix? - if GlobalVarConst.REFERENCE_NAME in bpy.data.materials: - bpy.data.materials.remove(bpy.data.materials[GlobalVarConst.REFERENCE_NAME]) + # TODO: This is the exception to splitting the setup and + # remove operations into separate functions, maybe can fix? + if Global.REFERENCE_NAME in bpy.data.materials: + bpy.data.materials.remove(bpy.data.materials[Global.REFERENCE_NAME]) # Grid for better snapping and measurements - if grabDoc.gridSubdivisions and grabDoc.useGrid: + if gd.gridSubdivisions and gd.useGrid: # Create & load new bmesh bm = bmesh.new() bm.from_mesh(plane_ob.data) # Subdivide - bmesh.ops.subdivide_edges(bm, edges=bm.edges, cuts=grabDoc.gridSubdivisions, use_grid_fill=True) + bmesh.ops.subdivide_edges( + bm, + edges=bm.edges, + cuts=gd.gridSubdivisions, + use_grid_fill=True + ) # Write back to the mesh bm.to_mesh(plane_ob.data) @@ -237,35 +267,39 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f # CAMERA # Add Trim Camera & change settings - trim_cam_data = bpy.data.cameras.new(GlobalVarConst.TRIM_CAMERA_NAME) - trim_cam_ob = bpy.data.objects.new(GlobalVarConst.TRIM_CAMERA_NAME, trim_cam_data) + trim_cam_data = bpy.data.cameras.new(Global.TRIM_CAMERA_NAME) + trim_cam_ob = bpy.data.objects.new(Global.TRIM_CAMERA_NAME, trim_cam_data) - trim_cam_ob.location = (0, 0, 15 * grabDoc.scalingSet) + trim_cam_ob.location = (0, 0, 15 * gd.scalingSet) trim_cam_ob.parent = plane_ob trim_cam_ob.is_gd_object = True - #trim_cam_ob.hide_viewport = trim_cam_ob.hide_select = True # TODO This occasionally causes visual errors when in camera view? + # TODO: This occasionally causes visual errors when in camera view? + #trim_cam_ob.hide_viewport = trim_cam_ob.hide_select = True trim_cam_data.type = 'ORTHO' trim_cam_data.display_size = .01 trim_cam_data.passepartout_alpha = 1 - trim_cam_data.ortho_scale = grabDoc.scalingSet + trim_cam_data.ortho_scale = gd.scalingSet trim_cam_data.clip_start = 0.1 - trim_cam_data.clip_end = 1000 * (grabDoc.scalingSet / 25) # Match scale to cameras clipping distance + # NOTE: Match scale to cameras clipping distance + trim_cam_data.clip_end = 1000 * (gd.scalingSet / 25) gd_coll.objects.link(trim_cam_ob) context.scene.camera = trim_cam_ob if reposition_cam: - bpy.ops.view3d.view_camera() # Needed to do twice or else Blender decides where the users viewport camera is located when they exit + # NOTE: Needed to do twice or else Blender decides where + # the users viewport camera is located when they exit + bpy.ops.view3d.view_camera() # HEIGHT GUIDE - if grabDoc.exportHeight and grabDoc.rangeTypeHeight == 'MANUAL': - generate_manual_height_guide_mesh(GlobalVarConst.HEIGHT_GUIDE_NAME, plane_ob) + if gd.exportHeight and gd.rangeTypeHeight == 'MANUAL': + generate_manual_height_guide_mesh(Global.HEIGHT_GUIDE_NAME, plane_ob) # ORIENT GUIDE - generate_plane_orient_guide_mesh(GlobalVarConst.ORIENT_GUIDE_NAME, plane_ob) + generate_plane_orient_guide_mesh(Global.ORIENT_GUIDE_NAME, plane_ob) # NODE GROUPS @@ -291,18 +325,25 @@ def scene_setup(self, context: types.Context) -> None: # Needs self for update f pass # Hide collections & make unselectable if requested (run this after everything else) - gd_coll.hide_select = not grabDoc.collSelectable - gd_coll.hide_viewport = not grabDoc.collVisible - gd_coll.hide_render = not grabDoc.collRendered + gd_coll.hide_select = not gd.collSelectable + gd_coll.hide_viewport = not gd.collVisible + gd_coll.hide_render = not gd.collRendered def generate_manual_height_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) -> None: - """Generate a mesh that gauges the height map range. This is for the "Manual" height map mode and can better inform a correct 0-1 range""" + """Generate a mesh that gauges the height map range. This + is for the "Manual" height map mode and can better inform + a correct 0-1 range""" # Make a tuple for the planes vertex positions - camera_corner_vecs = bpy.data.objects[GlobalVarConst.TRIM_CAMERA_NAME].data.view_frame(scene = bpy.context.scene) + camera_corner_vecs = \ + bpy.data.objects[Global.TRIM_CAMERA_NAME].data.view_frame(scene = bpy.context.scene) - stems_vecs = [(vec[0], vec[1], bpy.context.scene.grabDoc.guideHeight) for vec in camera_corner_vecs] - ring_vecs = [(vec[0], vec[1], vec[2] + 1) for vec in camera_corner_vecs] + stems_vecs = [ + (vec[0], vec[1], bpy.context.scene.grabDoc.guideHeight) for vec in camera_corner_vecs + ] + ring_vecs = [ + (vec[0], vec[1], vec[2] + 1) for vec in camera_corner_vecs + ] # Combine both tuples ring_vecs += stems_vecs @@ -330,7 +371,8 @@ def generate_manual_height_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) def generate_plane_orient_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) -> None: - """Generate a mesh that sits beside the background plane to guide the user to the correct "up" orientation""" + """Generate a mesh that sits beside the background plane + to guide the user to the correct "up" orientation""" # Create new mesh & object data blocks new_mesh = bpy.data.meshes.new(ob_name) new_ob = bpy.data.objects.new(ob_name, new_mesh)