From 11fa69acebf0429355884f7e331585d53d76115d Mon Sep 17 00:00:00 2001 From: Ethan Simon Date: Mon, 1 Jan 2024 17:07:05 -0500 Subject: [PATCH] Interim Submit --- .gitignore | 2 + __init__.py | 2 +- constants.py | 105 ++-- operators/marmoset.py | 81 +-- operators/material.py | 55 +- operators/operators.py | 436 +++++++------- preferences.py | 1238 +++++++++++++++++++--------------------- ui.py | 767 ++++++++++--------------- utils/baker.py | 257 +++++---- utils/generic.py | 71 ++- utils/node.py | 584 ++++++++++--------- utils/render.py | 72 +-- utils/scene.py | 119 ++-- 13 files changed, 1820 insertions(+), 1969 deletions(-) diff --git a/.gitignore b/.gitignore index 3a124a1..fef0507 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ README.md _temp/ grabdocgit_updater/ + +utils/temp/ diff --git a/__init__.py b/__init__.py index d9c8eb7..5f7000e 100644 --- a/__init__.py +++ b/__init__.py @@ -3,7 +3,7 @@ "author": "Ethan Simon-Law", "location": "3D View > Sidebar > GrabDoc", "version": (1, 4, 0), - "blender": (4, 0, 1), + "blender": (4, 0, 2), "tracker_url": "https://discord.com/invite/wHAyVZG", "category": "3D View" } diff --git a/constants.py b/constants.py index bc1c32e..4f9c373 100644 --- a/constants.py +++ b/constants.py @@ -9,38 +9,66 @@ NAME = "GrabDoc Pro" VERSION = (1, 4, 0) -BLENDER_VERSION = (4, 0, 1) +BLENDER_VERSION = (4, 0, 2) class GlobalVariableConstants: """A collection of constants used for global variable standardization""" ID_PREFIX = "grab_doc" # TODO: wrong prefix? - REFERENCE_NAME = "GD_Reference" - TRIM_CAMERA_NAME = "GD_Trim Camera" - BG_PLANE_NAME = "GD_Background Plane" + GD_PREFIX = "GD_" + GD_FLAG_PREFIX = "[GrabDoc] " + GD_LOW_PREFIX = GD_PREFIX + "low" + GD_HIGH_PREFIX = GD_PREFIX + "high" + + REFERENCE_NAME = GD_PREFIX + "Reference" + TRIM_CAMERA_NAME = GD_PREFIX + "Trim Camera" + BG_PLANE_NAME = GD_PREFIX + "Background Plane" + HEIGHT_GUIDE_NAME = GD_PREFIX + "Height Guide" + ORIENT_GUIDE_NAME = GD_PREFIX + "Orient Guide" + GD_MATERIAL_NAME = GD_PREFIX + "Material (do not touch contents)" 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_HIGH_PREFIX = "GD_high" - MAT_ID_PREFIX = "GD_ID" - MAT_ID_RAND_PREFIX = "GD_RANDOM_ID" + MAT_ID_PREFIX = GD_PREFIX + "ID" + MAT_ID_RAND_PREFIX = GD_PREFIX + "RANDOM_ID" + + REIMPORT_MAT_NAME = GD_FLAG_PREFIX + "Render Result" + + NORMAL_NAME = "Normal" + AO_NAME = "Ambient Occlusion" + HEIGHT_NAME = "Height" + ALPHA_NAME = "Alpha" + COLOR_NAME = "Base Color" + ROUGHNESS_NAME = "Roughness" + METALNESS_NAME = "Metalness" + + NORMAL_NG_NAME = GD_PREFIX + NORMAL_NAME + AO_NG_NAME = GD_PREFIX + AO_NAME + HEIGHT_NG_NAME = GD_PREFIX + HEIGHT_NAME + ALPHA_NG_NAME = GD_PREFIX + ALPHA_NAME + COLOR_NG_NAME = GD_PREFIX + COLOR_NAME + ROUGHNESS_NG_NAME = GD_PREFIX + ROUGHNESS_NAME + METALNESS_NG_NAME = GD_PREFIX + METALNESS_NAME + + ALL_MAP_NAMES = ( + NORMAL_NAME, + AO_NAME, + HEIGHT_NAME, + ALPHA_NAME, + COLOR_NAME, + ROUGHNESS_NAME, + METALNESS_NAME + ) - 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" + SHADER_MAP_NAMES = ( + COLOR_NG_NAME, + ROUGHNESS_NG_NAME, + METALNESS_NG_NAME, + NORMAL_NG_NAME + ) - INVALID_RENDER_TYPES = ( + INVALID_BAKE_TYPES = ( 'EMPTY', 'VOLUME', 'ARMATURE', @@ -50,7 +78,7 @@ class GlobalVariableConstants: 'CAMERA' ) - FORMAT_MATCHED_EXTENSIONS = { + IMAGE_FORMATS = { 'TIFF': 'tif', 'TARGA': 'tga', 'OPEN_EXR': 'exr', @@ -64,11 +92,12 @@ class GlobalVariableConstants: 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 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 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 grayscale maps can currently be packed @@ -79,41 +108,35 @@ class GlobalVariableConstants: bake maps, meaning without intervention G, B, and A channels will be empty.""" PREVIEW_WARNING = \ -"""Live Material Preview allows you to visualize your bake maps in real-time! - -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 the current project file.""" +"""Material Preview allows you to visualize your bake maps in real-time! +\u2022 This feature is intended for previewing your materials before baking, NOT +\u2022 for working while inside a preview. Once finished, please exit previews +\u2022 to avoid scene altering changes. +\u2022 Pressing OK will dismiss this warning permanently for the project.""" class ErrorCodeConstants: """A collection of constants used for error code/message standardization""" NO_OBJECTS_SELECTED = "There are no objects selected" - TRIM_CAM_NOT_FOUND = \ - "Trim Camera not found, refresh the scene to set everything up properly" + "GrabDoc Camera not found, please run the Refresh Scene operator" NO_OBJECTS_BAKE_GROUPS = \ - "You have 'Use Bake Group' turned on, but no objects are inside the corresponding collection" + "No objects found in bake collections" 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" - + "Material slots were found without links, using default values" 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!" - # ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or diff --git a/operators/marmoset.py b/operators/marmoset.py index 34cac54..918a24e 100644 --- a/operators/marmoset.py +++ b/operators/marmoset.py @@ -11,10 +11,10 @@ from ..constants import ErrorCodeConstants as Error from ..utils.generic import ( bad_setup_check, - export_bg_plane, + export_plane, get_create_addon_temp_dir ) -from ..utils.render import set_guide_height +from ..utils.render import set_guide_height, get_rendered_objects ################################################ @@ -40,49 +40,48 @@ class GrabDoc_OT_send_to_marmo(OpInfo, Operator): @classmethod def poll(cls, context: Context) -> bool: return os.path.exists( - context.preferences.addons[__package__].preferences.marmoEXE + context.preferences.addons[__package__].preferences.marmoset_executable ) def open_marmoset(self, context: Context, temps_path, addon_path): - gd = context.scene.grabDoc - marmo_exe = context.preferences.addons[__package__].preferences.marmoEXE + gd = context.scene.gd + marmo_exe = context.preferences.addons[__package__].preferences.marmoset_executable # 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), + 'file_path': f'{bpy.path.abspath(gd.export_path)}{gd.export_name}.{gd.marmoset_format.lower()}', + 'file_ext': gd.marmoset_format.lower(), + 'file_path_no_ext': bpy.path.abspath(gd.export_path), '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), + 'resolution_x': gd.export_res_x, + 'resolution_y': gd.export_res_y, + 'bits_per_channel': int(gd.depth), + 'samples': int(gd.marmoset_samples), - 'auto_bake': gd.marmoAutoBake, - 'close_after_bake': gd.marmoClosePostBake, - 'open_folder': gd.openFolderOnExport, + 'auto_bake': gd.metalness_auto_bake, + 'close_after_bake': gd.marmoset_auto_close, - 'export_normal': gd.exportNormals & gd.uiVisibilityNormals, - 'flipy_normal': gd.flipYNormals, - 'suffix_normal': gd.suffixNormals, + 'export_normal': gd.normals[0].enabled & gd.normals[0].ui_visibility, + 'flipy_normal': gd.normals[0].flip_y, + 'suffix_normal': gd.normals[0].suffix, - 'export_curvature': gd.exportCurvature & gd.uiVisibilityCurvature, - 'suffix_curvature': gd.suffixCurvature, + 'export_curvature': gd.curvature[0].enabled & gd.curvature[0].ui_visibility, + 'suffix_curvature': gd.curvature[0].suffix, - 'export_occlusion': gd.exportOcclusion & gd.uiVisibilityOcclusion, - 'ray_count_occlusion': gd.marmoAORayCount, - 'suffix_occlusion': gd.suffixOcclusion, + 'export_occlusion': gd.occlusion[0].enabled & gd.occlusion[0].ui_visibility, + 'ray_count_occlusion': gd.marmoset_occlusion_ray_count, + 'suffix_occlusion': gd.occlusion[0].suffix, - 'export_height': gd.exportHeight & gd.uiVisibilityHeight, - 'cage_height': gd.guideHeight * 100 * 2, - 'suffix_height': gd.suffixHeight, + 'export_height': gd.height[0].enabled & gd.height[0].ui_visibility, + 'cage_height': gd.height[0].distance * 100 * 2, + 'suffix_height': gd.height[0].suffix, - 'export_alpha': gd.exportAlpha & gd.uiVisibilityAlpha, - 'suffix_alpha': gd.suffixAlpha, + 'export_alpha': gd.alpha[0].enabled & gd.alpha[0].ui_visibility, + 'suffix_alpha': gd.alpha[0].suffix, - 'export_matid': gd.exportMatID & gd.uiVisibilityMatID, - 'suffix_id': gd.suffixID + 'export_matid': gd.id[0].enabled & gd.id[0].ui_visibility, + 'suffix_id': gd.id[0].suffix } # Flip the slashes of the first Dict value (It's @@ -103,8 +102,8 @@ def open_marmoset(self, context: Context, temps_path, addon_path): path_ext_only = os.path.basename(os.path.normpath(marmo_exe)).encode() - if gd.exportPlane: - export_bg_plane(context) + if gd.export_plane: + export_plane(context) subproc_args = [ marmo_exe, @@ -128,9 +127,10 @@ def open_marmoset(self, context: Context, temps_path, addon_path): return {'FINISHED'} def execute(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd - report_value, report_string = bad_setup_check(self, context, active_export=True) + report_value, report_string = \ + bad_setup_check(context, active_export=True) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} @@ -142,14 +142,15 @@ def execute(self, context: Context): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') - if gd.exportHeight and gd.rangeTypeHeight == 'AUTO': - set_guide_height() + rendered_obs = get_rendered_objects() + if gd.height[0].enabled and gd.height[0].method == 'AUTO': + set_guide_height(rendered_obs) # Set high poly naming for ob in context.view_layer.objects: ob.select_set(False) - if ob.name in self.rendered_obs \ + if ob.name in rendered_obs \ and ob.visible_get() and ob.name != Global.BG_PLANE_NAME: ob.select_set(True) @@ -165,7 +166,8 @@ def execute(self, context: Context): # 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.name = \ + f"{Global.GD_HIGH_PREFIX} {Global.BG_PLANE_NAME}" bg_plane_ob_copy.select_set(True) # Remove reference material @@ -185,13 +187,12 @@ def execute(self, context: Context): 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: + if not gd.coll_selectable: bpy.data.collections[Global.COLL_NAME].hide_select = True for ob_name in saved_selected: diff --git a/operators/material.py b/operators/material.py index 4a6fc2f..1f5d6dd 100644 --- a/operators/material.py +++ b/operators/material.py @@ -5,16 +5,22 @@ from .operators import OpInfo from ..constants import GlobalVariableConstants as Global -from ..utils.generic import UseSelectedOnly, get_rendered_objects +from ..utils.generic import UseSelectedOnly +from ..utils.render import get_rendered_objects -def generate_random_id(id_prefix: str='ID') -> str: +def generate_random_name( + prefix: str='ID', + minimum: int=1000, + maximum: int=100000 + ) -> str: """Generates a random id map name based on a given prefix""" while True: - new_mat_name = f"{id_prefix}.{randint(1000, 100000)}" - if new_mat_name not in bpy.data.materials: + name = f"{prefix}.{randint(minimum, maximum)}" + if name not in bpy.data.materials: break - return new_mat_name + return name + class GRABDOC_OT_quick_id_setup(OpInfo, Operator): @@ -27,26 +33,23 @@ def execute(self, context: Context): 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: + rendered_obs = get_rendered_objects() + for ob in rendered_obs: add_mat = True - - if not ob.name.startswith(Global.GD_PREFIX) \ - and ob.name in self.rendered_obs: + if not ob.name.startswith(Global.GD_PREFIX): for slot in ob.material_slots: - # If a manual ID exists on the object, ignore it 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(Global.MAT_ID_RAND_PREFIX) + generate_random_name(Global.MAT_ID_RAND_PREFIX) ) mat.use_nodes = True - mat.diffuse_color = (random(), random(), random(), 1) # Viewport color + # NOTE: Viewport color + mat.diffuse_color = (random(), random(), random(), 1) bsdf = mat.node_tree.nodes.get('Principled BSDF') bsdf.inputs[0].default_value = mat.diffuse_color @@ -55,7 +58,9 @@ def execute(self, context: Context): ob.active_material = mat for mat in bpy.data.materials: - if (mat.name.startswith(Global.MAT_ID_PREFIX) or mat.name.startswith(Global.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'} @@ -66,7 +71,9 @@ class GRABDOC_OT_quick_id_selected(OpInfo, UseSelectedOnly, Operator): bl_label = "Add ID to Selected" def execute(self, context: Context): - mat = bpy.data.materials.new(generate_random_id(Global.MAT_ID_PREFIX)) + mat = bpy.data.materials.new( + generate_random_name(Global.MAT_ID_PREFIX) + ) mat.use_nodes = True mat.diffuse_color = (random(), random(), random(), 1) @@ -79,7 +86,9 @@ def execute(self, context: Context): ob.active_material = mat for mat in bpy.data.materials: - if (mat.name.startswith(Global.MAT_ID_PREFIX) or mat.name.startswith(Global.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'} @@ -105,11 +114,13 @@ class GRABDOC_OT_quick_remove_selected_mats(OpInfo, UseSelectedOnly, Operator): 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(Global.MAT_ID_PREFIX): - bpy.data.materials.remove(bpy.data.materials[slot.name]) - break + if ob.type not in ('MESH', 'CURVE'): + continue + for slot in ob.material_slots: + if not slot.name.startswith(Global.MAT_ID_PREFIX): + continue + bpy.data.materials.remove(bpy.data.materials[slot.name]) + break return {'FINISHED'} diff --git a/operators/operators.py b/operators/operators.py index 1f3fc95..b867052 100644 --- a/operators/operators.py +++ b/operators/operators.py @@ -6,8 +6,18 @@ from bpy.types import SpaceView3D, Event, Context, Operator, UILayout from bpy.props import EnumProperty +from ..utils.render import get_rendered_objects from ..utils.node import cleanup_ng_from_mat from ..utils.scene import scene_setup, remove_setup +from ..utils.generic import ( + proper_scene_setup, + bad_setup_check, + export_plane, + get_format, + is_camera_in_3d_view, + get_create_addon_temp_dir, + poll_message_error +) from ..utils.baker import ( export_and_preview_setup, normals_setup, @@ -18,24 +28,15 @@ height_setup, alpha_setup, id_setup, - albedo_setup, + color_setup, roughness_setup, metalness_setup, - reimport_as_material, + #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, - get_format_extension, - is_camera_in_3d_view, - get_create_addon_temp_dir, - poll_message_error -) class OpInfo: @@ -59,7 +60,7 @@ 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 = \ + context.scene.gd.reference = \ bpy.data.images[os.path.basename(os.path.normpath(self.filepath))] return {'FINISHED'} @@ -76,7 +77,7 @@ class GRABDOC_OT_open_folder(OpInfo, Operator): def execute(self, context: Context): try: bpy.ops.wm.path_open( - filepath=bpy.path.abspath(context.scene.grabDoc.exportPath) + filepath=bpy.path.abspath(context.scene.gd.export_path) ) except RuntimeError: self.report({'ERROR'}, Error.NO_VALID_PATH_SET) @@ -146,10 +147,10 @@ class GRABDOC_OT_export_maps(OpInfo, Operator, UILayout): @classmethod def poll(cls, context: Context) -> bool: - return not context.scene.grabDoc.modalState + return not context.scene.gd.preview_state def grabdoc_export(self, context: Context, export_suffix: str) -> None: - gd = context.scene.grabDoc + gd = context.scene.gd render = context.scene.render # Save - file output path @@ -158,7 +159,7 @@ def grabdoc_export(self, context: Context, export_suffix: str) -> None: # 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 + bpy.path.abspath(gd.export_path) + gd.export_name + '_' + export_suffix context.scene.camera = bpy.data.objects[Global.TRIM_CAMERA_NAME] @@ -172,32 +173,33 @@ def draw(self, context: Context): print("Layout:", layout) def execute(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd - #print(self.map_types) self.map_types = 'export' - print(self.map_types) - report_value, report_string = bad_setup_check(self, context, active_export=True) + report_value, report_string = \ + bad_setup_check(context, active_export=True) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} + rendered_objects = get_rendered_objects() + # Start execution timer & UI progress bar start = time.time() context.window_manager.progress_begin(0, 9999) # System for dynamically deciding the progress percentage operation_counter = ( - (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) + (gd.normals[0].ui_visibility and gd.normals[0].enabled), + (gd.curvature[0].ui_visibility and gd.curvature[0].enabled), + (gd.occlusion[0].ui_visibility and gd.occlusion[0].enabled), + (gd.height[0].ui_visibility and gd.height[0].enabled), + (gd.alpha[0].ui_visibility and gd.alpha[0].enabled), + (gd.id[0].ui_visibility and gd.id[0].enabled), + (gd.color[0].ui_visibility and gd.color[0].enabled), + (gd.roughness[0].ui_visibility and gd.roughness[0].enabled), + (gd.metalness[0].ui_visibility and gd.metalness[0].enabled) ) percentage_division = 100 / (1 + sum(operation_counter)) @@ -222,82 +224,82 @@ def execute(self, context: Context): percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityNormals and gd.exportNormals: - normals_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixNormals) + if gd.normals[0].ui_visibility and gd.normals[0].enabled: + normals_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.normals[0].suffix) - if gd.reimportAsMatNormals: - reimport_as_material(gd.suffixNormals) + #if gd.normals[0].reimport: + # reimport_as_material(gd.normals[0].suffix) - cleanup_ng_from_mat(Global.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NORMAL_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityCurvature and gd.exportCurvature: - curvature_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixCurvature) - curvature_refresh(self, context) + if gd.curvature[0].ui_visibility and gd.curvature[0].enabled: + curvature_setup(self) + self.grabdoc_export(context, export_suffix=gd.curvature[0].suffix) + curvature_refresh(self) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityOcclusion and gd.exportOcclusion: - occlusion_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixOcclusion) + if gd.occlusion[0].ui_visibility and gd.occlusion[0].enabled: + occlusion_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.occlusion[0].suffix) - if gd.reimportAsMatOcclusion: - reimport_as_material(gd.suffixOcclusion) + #if gd.occlusion[0].reimport: + # reimport_as_material(gd.occlusion[0].suffix) - cleanup_ng_from_mat(Global.NG_AO_NAME) - occlusion_refresh(self, context) + cleanup_ng_from_mat(Global.AO_NG_NAME) + occlusion_refresh(self) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityHeight and gd.exportHeight: - height_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixHeight) - cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) + if gd.height[0].ui_visibility and gd.height[0].enabled: + height_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.height[0].suffix) + cleanup_ng_from_mat(Global.HEIGHT_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityAlpha and gd.exportAlpha: - alpha_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixAlpha) - cleanup_ng_from_mat(Global.NG_ALPHA_NAME) + if gd.alpha[0].ui_visibility and gd.alpha[0].enabled: + alpha_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.alpha[0].suffix) + cleanup_ng_from_mat(Global.ALPHA_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityMatID and gd.exportMatID: - id_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixID) + if gd.id[0].ui_visibility and gd.id[0].enabled: + id_setup() + self.grabdoc_export(context, export_suffix=gd.id[0].suffix) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityAlbedo and gd.exportAlbedo: - albedo_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixAlbedo) - cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) + if gd.color[0].ui_visibility and gd.color[0].enabled: + color_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.color[0].suffix) + cleanup_ng_from_mat(Global.COLOR_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityRoughness and gd.exportRoughness: - roughness_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixRoughness) - cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) + if gd.roughness[0].ui_visibility and gd.roughness[0].enabled: + roughness_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.roughness[0].suffix) + cleanup_ng_from_mat(Global.ROUGHNESS_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) - if gd.uiVisibilityMetalness and gd.exportMetalness: - metalness_setup(self, context) - self.grabdoc_export(context, export_suffix=gd.suffixMetalness) - cleanup_ng_from_mat(Global.NG_METALNESS_NAME) + if gd.metalness[0].ui_visibility and gd.metalness[0].enabled: + metalness_setup(self, rendered_objects) + self.grabdoc_export(context, export_suffix=gd.metalness[0].suffix) + cleanup_ng_from_mat(Global.METALNESS_NG_NAME) percent_till_completed += percentage_division context.window_manager.progress_update(percent_till_completed) @@ -308,11 +310,8 @@ def execute(self, context: Context): plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 1 - if gd.exportPlane: - export_bg_plane(context) - - if gd.openFolderOnExport: - bpy.ops.wm.path_open(filepath=bpy.path.abspath(gd.exportPath)) + if gd.export_plane: + export_plane(context) # Call for Original Context Mode, use bpy.ops # so that Blenders viewport refreshes @@ -346,9 +345,9 @@ class MapEnum(): ('curvature', "Curvature", ""), ('occlusion', "Ambient Occlusion", ""), ('height', "Height", ""), - ('ID', "Material ID", ""), + ('id', "Material ID", ""), ('alpha', "Alpha", ""), - ('albedo', "Albedo", ""), + ('color', "Color", ""), ('roughness', "Roughness", ""), ('metalness', "Metalness", "") ), @@ -365,11 +364,11 @@ class GRABDOC_OT_offline_render(OpInfo, MapEnum, Operator): # - Might be able to have this uniquely # utilize compositing for mixing maps? # - Support Reimport as Materials - # - Support correct default colorspaces + # - Support correct default color spaces @classmethod def poll(cls, context: Context) -> bool: - return True if not context.scene.grabDoc.modalState else poll_message_error( + return True if not context.scene.gd.preview_state else poll_message_error( cls, "Cannot render, in a Modal State" ) @@ -383,7 +382,7 @@ def offline_render(self): image_name = 'GD_Render Result' - render.filepath = os.path.join(temps_path, image_name + get_format_extension()) + render.filepath = os.path.join(temps_path, image_name + get_format()) # Delete original image if image_name in bpy.data.images: @@ -408,7 +407,7 @@ def offline_render(self): def execute(self, context: Context): report_value, report_string = \ - bad_setup_check(self, context, active_export=False) + bad_setup_check(context, active_export=False) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} @@ -432,44 +431,47 @@ def execute(self, context: Context): plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 3 - # NOTE: Match case exists, looks slightly better vs conditional in this case + rendered_objects = get_rendered_objects() + + # NOTE: Match case exists, looks slightly + # better vs conditional in this case match self.map_types: case "normals": - normals_setup(self, context) + normals_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NORMAL_NG_NAME) case "curvature": - curvature_setup(self, context) + curvature_setup(self) self.offline_render() - curvature_refresh(self, context) + curvature_refresh(self) case "occlusion": - occlusion_setup(self, context) + occlusion_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_AO_NAME) - occlusion_refresh(self, context) + cleanup_ng_from_mat(Global.AO_NG_NAME) + occlusion_refresh(self) case "height": - height_setup(self, context) + height_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) + cleanup_ng_from_mat(Global.HEIGHT_NG_NAME) case "ID": - id_setup(self, context) + id_setup() self.offline_render() case "alpha": - alpha_setup(self, context) + alpha_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_ALPHA_NAME) - case "albedo": - albedo_setup(self, context) + cleanup_ng_from_mat(Global.ALPHA_NG_NAME) + case "color": + color_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) + cleanup_ng_from_mat(Global.COLOR_NG_NAME) case "roughness": - roughness_setup(self, context) + roughness_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) + cleanup_ng_from_mat(Global.ROUGHNESS_NG_NAME) case "metalness": - metalness_setup(self, context) + metalness_setup(self, rendered_objects) self.offline_render() - cleanup_ng_from_mat(Global.NG_METALNESS_NAME) + cleanup_ng_from_mat(Global.METALNESS_NG_NAME) # Refresh all original settings export_refresh(self, context) @@ -512,7 +514,7 @@ def execute(self, context: Context): # 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 - context.scene.grabDoc.modalState = False + context.scene.gd.preview_state = False return {'FINISHED'} @@ -526,13 +528,14 @@ def invoke(self, context: Context, _event: Event): return context.window_manager.invoke_props_dialog(self, width=525) def draw(self, _context: Context): - col = self.layout.column(align=True) - for line in Global.PREVIEW_WARNING.split('\n')[1:][:-1]: - col.label(text=line) + self.layout.label(text=Global.PREVIEW_WARNING) + #col = self.layout.column(align=True) + #for line in Global.PREVIEW_WARNING.split('\n')[1:][:-1]: + # col.label(text=line) + # col.separator() def execute(self, context: Context): - context.scene.grabDoc.firstBakePreview = False - + context.scene.gd.preview_first_time = False bpy.ops.grab_doc.preview_map(map_types=self.map_types) return {'FINISHED'} @@ -541,8 +544,6 @@ 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" font_id = 0 font_size = 25 @@ -551,31 +552,30 @@ def draw_callback_px(self, context: Context) -> None: font_y_pos = 80 font_pos_offset = 50 - # Manually handle small viewports + # Handle small viewports for area in context.screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'WINDOW': - if region.width < 700 or region.height < 400: - font_size = 15 - font_pos_offset = 30 - break + if area.type != 'VIEW_3D': + continue + for region in area.regions: + if region.type != 'WINDOW': + continue + if region.width <= 700 or region.height <= 400: + font_size *= .5 + font_pos_offset *= .5 + break - # Enable shadow blf.enable(font_id, 4) blf.shadow(font_id, 0, *(0, 0, 0, font_opacity)) - - # Draw text blf.position(font_id, font_x_pos, (font_y_pos + font_pos_offset), 0) blf.size(font_id, font_size) blf.color(font_id, *(1, 1, 1, font_opacity)) blf.draw(font_id, f"{render_text} Preview | [ESC] to exit") - blf.position(font_id, font_x_pos, font_y_pos, 0) blf.size(font_id, font_size+1) 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, Operator): """Preview the selected material""" bl_idname = "grab_doc.preview_map" @@ -583,82 +583,74 @@ class GRABDOC_OT_map_preview(OpInfo, MapEnum, Operator): def modal(self, context: Context, event: Event): scene = context.scene - gd = scene.grabDoc + gd = scene.gd 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 gd.collRendered: + image_settings = scene.render.image_settings + if not gd.coll_rendered: scene.render.film_transparent = True - image_settings.color_mode = 'RGBA' else: scene.render.film_transparent = False - image_settings.color_mode = 'RGB' # Get correct file format and color depth - image_settings.file_format = gd.imageType - - if gd.imageType == 'OPEN_EXR': - image_settings.color_depth = gd.colorDepthEXR - elif gd.imageType != 'TARGA': - image_settings.color_depth = gd.colorDepth + image_settings.file_format = gd.format + if gd.format == 'OPEN_EXR': + image_settings.color_depth = gd.exr_depth + elif gd.format != 'TARGA': + image_settings.color_depth = gd.depth # Bake map specific settings that are forcibly kept in check # TODO: update the node group input values for Mixed Normal view_settings = scene.view_settings - if self.map_types == "curvature": - view_settings.look = gd.contrastCurvature.replace('_', ' ') - + view_settings.look = gd.curvature[0].contrast.replace('_', ' ') bpy.data.objects[Global.BG_PLANE_NAME].color[3] = .9999 - elif self.map_types == "occlusion": - view_settings.look = gd.contrastOcclusion.replace('_', ' ') - - #ao = bpy.data.node_groups[NG_AO_NAME].nodes.get('Ambient Occlusion') - #ao.inputs[1].default_value = gd.distanceOcclusion - + view_settings.look = gd.occlusion[0].contrast.replace('_', ' ') + #ao = \ + # bpy.data.node_groups[NG_AO_NAME].nodes.get('Ambient Occlusion') + #ao.inputs[1].default_value = gd.occlusion[0].distance elif self.map_types == "height": - view_settings.look = gd.contrastHeight.replace('_', ' ') - + view_settings.look = gd.height[0].contrast.replace('_', ' ') elif self.map_types == "ID": - scene.display.shading.color_type = gd.methodMatID + scene.display.shading.color_type = gd.id[0].method # Exit check - if not gd.modalState or event.type in {'ESC'} or not proper_scene_setup(): + if not gd.preview_state \ + or event.type in {'ESC'} \ + or not proper_scene_setup(): self.modal_cleanup(context) return {'CANCELLED'} return {'PASS_THROUGH'} def modal_cleanup(self, context: Context) -> None: - gd = context.scene.grabDoc - - gd.modalState = False + gd = context.scene.gd + gd.preview_state = False SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') if self.map_types == "normals": - cleanup_ng_from_mat(Global.NG_NORMAL_NAME) + cleanup_ng_from_mat(Global.NORMAL_NG_NAME) elif self.map_types == "curvature": - curvature_refresh(self, context) + curvature_refresh(self) elif self.map_types == "occlusion": - occlusion_refresh(self, context) - cleanup_ng_from_mat(Global.NG_AO_NAME) + occlusion_refresh(self) + cleanup_ng_from_mat(Global.AO_NG_NAME) elif self.map_types == "height": - cleanup_ng_from_mat(Global.NG_HEIGHT_NAME) + cleanup_ng_from_mat(Global.HEIGHT_NG_NAME) elif self.map_types == "alpha": - cleanup_ng_from_mat(Global.NG_ALPHA_NAME) - elif self.map_types == "albedo": - cleanup_ng_from_mat(Global.NG_ALBEDO_NAME) + cleanup_ng_from_mat(Global.ALPHA_NG_NAME) + elif self.map_types == "color": + cleanup_ng_from_mat(Global.COLOR_NG_NAME) elif self.map_types == "roughness": - cleanup_ng_from_mat(Global.NG_ROUGHNESS_NAME) + cleanup_ng_from_mat(Global.ROUGHNESS_NG_NAME) elif self.map_types == "metalness": - cleanup_ng_from_mat(Global.NG_METALNESS_NAME) + cleanup_ng_from_mat(Global.METALNESS_NG_NAME) export_refresh(self, context) @@ -677,23 +669,23 @@ def modal_cleanup(self, context: Context) -> None: space.shading.type = self.saved_render_view break - gd.bakerType = self.savedBakerType + gd.baker_type = self.savedBakerType # 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(): + if gd.preview_auto_exit_camera or not proper_scene_setup(): bpy.ops.grab_doc.view_cam(from_modal=True) def execute(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd report_value, report_string = \ - bad_setup_check(self, context, active_export=False) + bad_setup_check(context, active_export=False) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} - gd.modalState = True + gd.preview_state = True if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') @@ -714,30 +706,32 @@ def execute(self, context: Context): break # Save & Set - UI Baker Type - self.savedBakerType = gd.bakerType - gd.bakerType = 'Blender' + self.savedBakerType = gd.baker_type + gd.baker_type = 'Blender' # Set - Preview type - gd.modalPreviewType = self.map_types + gd.preview_type = self.map_types + + rendered_objects = get_rendered_objects() if self.map_types == 'normals': - normals_setup(self, context) + normals_setup(self, rendered_objects) elif self.map_types == 'curvature': - curvature_setup(self, context) + curvature_setup(self) elif self.map_types == 'occlusion': - occlusion_setup(self, context) + occlusion_setup(self, rendered_objects) elif self.map_types == 'height': - height_setup(self, context) + height_setup(self, rendered_objects) elif self.map_types == 'alpha': - alpha_setup(self, context) - elif self.map_types == 'albedo': - albedo_setup(self, context) + alpha_setup(self, rendered_objects) + elif self.map_types == 'color': + color_setup(self, rendered_objects) elif self.map_types == 'roughness': - roughness_setup(self, context) + roughness_setup(self, rendered_objects) elif self.map_types == 'metalness': - metalness_setup(self, context) + metalness_setup(self, rendered_objects) else: # ID - id_setup(self, context) + id_setup() # Draw text handler self._handle = SpaceView3D.draw_handler_add( @@ -756,74 +750,62 @@ class GRABDOC_OT_export_current_preview(OpInfo, Operator): @classmethod def poll(cls, context: Context) -> bool: - return context.scene.grabDoc.modalState + return context.scene.gd.preview_state def execute(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd - report_value, report_string = bad_setup_check(self, context, active_export=True) + report_value, report_string = bad_setup_check(context, active_export=True) if report_value: self.report({'ERROR'}, report_string) return {'CANCELLED'} - # Start counting execution time start = time.time() - # Save - file output path - render = context.scene.render - saved_path = render.filepath - - 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 + # NOTE: Scale plane to account for overscan 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) + if gd.preview_type == 'normals': + suffix = gd.normals[0].suffix + elif gd.preview_type == 'curvature': + suffix = gd.curvature[0].suffix + elif gd.preview_type == 'occlusion': + suffix = gd.occlusion[0].suffix + elif gd.preview_type == 'height': + suffix = gd.height[0].suffix + elif gd.preview_type == 'ID': + suffix = gd.id[0].suffix + elif gd.preview_type == 'alpha': + suffix = gd.alpha[0].suffix + elif gd.preview_type == 'color': + suffix = gd.color[0].suffix + elif gd.preview_type == 'roughness': + suffix = gd.roughness[0].suffix + elif gd.preview_type == 'metalness': + suffix = gd.metalness[0].suffix - # Refresh - file output path + render = context.scene.render + saved_path = render.filepath + render.filepath = \ + os.path.join( + bpy.path.abspath(gd.export_path), + gd.export_name + "_" + suffix + ) + bpy.ops.render.render(write_still=True) render.filepath = saved_path - - # Reimport the Normal/Occlusion map as a material if requested - 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[Global.BG_PLANE_NAME] plane_ob.scale[0] = plane_ob.scale[1] = 1 - if gd.exportPlane: - export_bg_plane(context) + # Reimport the Normal/Occlusion map as a material if requested + #if gd.preview_type == 'normals' and gd.normals[0].reimport: + # reimport_as_material(gd.normals[0].suffix) + #elif gd.preview_type == 'occlusion' and gd.occlusion[0].reimport: + # reimport_as_material(gd.occlusion[0].suffix) + #reimport_as_material() - # Open export path location if requested - if gd.openFolderOnExport: - bpy.ops.wm.path_open(filepath=bpy.path.abspath(gd.exportPath)) + if gd.export_plane: + export_plane(context) - # End the timer end = time.time() exc_time = round(end - start, 2) @@ -867,7 +849,7 @@ def execute(self, context: Context): GRABDOC_OT_map_preview_warning, GRABDOC_OT_map_preview, GRABDOC_OT_leave_map_preview, - GRABDOC_OT_export_current_preview + GRABDOC_OT_export_current_preview, #GRABDOC_OT_map_pack_info ) diff --git a/preferences.py b/preferences.py index 8e07b11..f5e5ebf 100644 --- a/preferences.py +++ b/preferences.py @@ -18,6 +18,7 @@ from bpy.props import ( BoolProperty, PointerProperty, + CollectionProperty, StringProperty, EnumProperty, IntProperty, @@ -39,7 +40,7 @@ class GRABDOC_MT_presets(Menu): bl_label = "" - preset_subdir = "grabDoc" + preset_subdir = "gd" preset_operator = "script.execute_preset" draw = Menu.draw_preset @@ -57,114 +58,53 @@ class GRABDOC_OT_add_preset(AddPresetBase, Operator): preset_menu = "GRABDOC_MT_presets" # Variable used for all preset values - preset_defines = ["grabDoc=bpy.context.scene.grabDoc"] - - # TODO: Create a function that - # generates this list below + preset_defines = [ + "gd = bpy.context.scene.gd" + ] - # Properties to store in the preset + # Properties to store + # TODO: Create a function + # to generate list preset_values = [ - "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" + "gd.coll_selectable", + "gd.coll_visible", + "gd.coll_rendered", + "gd.use_grid", + "gd.grid_subdivs", + "gd.filter", + "gd.filter_width", + "gd.scale", + + "gd.baker_type", + "gd.export_path", + "gd.export_name", + "gd.export_res_x", + "gd.export_res_y", + "gd.lock_res", + "gd.format", + "gd.depth", + "gd.tga_depth", + "gd.png_compression", + + "gd.use_bake_collections", + "gd.export_plane", + "gd.preview_auto_exit_camera", + + "gd.marmoset_auto_bake", + "gd.marmoset_auto_close", + "gd.marmoset_samples", + "gd.marmoset_occlusion_ray_count", + "gd.marmoset_format", + + "gd.normals", + "gd.curvature", + "gd.occlusion", + "gd.height", + "gd.alpha", + "gd.id", + "gd.color", + "gd.roughness", + "gd.metalness", ] # Where to store the preset @@ -189,34 +129,33 @@ def execute(self, _context: Context): class GRABDOC_MT_addon_preferences(AddonPreferences): bl_idname = __package__ - # Store this here so the value is saved across project files - marmoEXE: StringProperty( + # NOTE: Special properties stored + # here are saved in User Preferences + # AKA across project files + + marmoset_executable: StringProperty( name="", - description="Changes the name of the exported file", - default="", + description="", + default="Path to Marmoset Toolbag 3 or 4 executable", subtype="FILE_PATH" ) def draw(self, _context: Context): layout = self.layout row = layout.row() - 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.label(text="There is a GrabDoc update available!") elif not updater.update_ready: - row.label( - text="You have the latest version of GrabDoc! There are no new versions available." + row.label(text="You have the latest version of GrabDoc!") + row.operator( + "updater_gd.check_for_update", + text="", + icon="FILE_REFRESH" ) - row.operator("updater_gd.check_for_update", - text="", icon="FILE_REFRESH") ############################################################ @@ -224,275 +163,580 @@ def draw(self, _context: Context): ############################################################ -class GRABDOC_property_group(PropertyGroup): - # UPDATE FUNCTIONS - - def update_scaling_set(self, context: Context): - scene_setup(self, context) +class GRABDOC_baker_defaults(): + NAME = "" + ALIAS = NAME.capitalize() + MARMOSET_COMPATIBLE = True + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", ""), + ('workbench', "Workbench", "") + ) - 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) + def update_engine(self, context: Context): + if not context.scene.gd.preview_state: + return + context.scene.render.engine = str(self.engine).upper() - 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 + enabled: BoolProperty(name="Export Enabled", default=True) + ui_visibility: BoolProperty(default=True) + reimport: BoolProperty( + name="Reimport Texture", + description="Reimport bake map texture into a Blender material" + ) + suffix: StringProperty( + name="Suffix", + description="The suffix of the exported bake map", + default=NAME # NOTE: Set on item creation + ) + contrast: EnumProperty( + items=( + ('None', "None (Medium)", ""), + ('Very_High_Contrast', "Very High", ""), + ('High_Contrast', "High", ""), + ('Medium_High_Contrast', "Medium High", ""), + ('Medium_Low_Contrast', "Medium Low", ""), + ('Low_Contrast', "Low", ""), + ('Very_Low_Contrast', "Very Low", "") + ), + name="Contrast" + ) + samples: IntProperty( + name="Eevee Samples", default=128, min=1, soft_max=512 + ) + samples_workbench: EnumProperty( + items=( + ('OFF', "No Anti-Aliasing", ""), + ('FXAA', "1 Sample", ""), + ('5', "5 Samples", ""), + ('8', "8 Samples", ""), + ('11', "11 Samples", ""), + ('16', "16 Samples", ""), + ('32', "32 Samples", "") + ), + default="16", + name="Workbench Samples" + ) + samples_cycles: IntProperty( + name="Cycles Samples", default=32, min=1, soft_max=1024 + ) + engine: EnumProperty( # NOTE: Add property to all subclasses + items=SUPPORTED_ENGINES, + name='Render Engine', + update=update_engine + ) - 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: Context): - if self.lockRes and self.exportResX != self.exportResY: - self.exportResY = self.exportResX - scene_setup(self, context) - - def update_res_y(self, context: Context): - if self.lockRes and self.exportResY != self.exportResX: - self.exportResX = self.exportResY - scene_setup(self, context) +class GRABDOC_normals(GRABDOC_baker_defaults, PropertyGroup): + NAME = "normals" + ALIAS = NAME.capitalize() + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) - def update_export_name(self, _context: Context): - if not self.exportName: - self.exportName = "untitled" + def update_flip_y(self, _context: Context): + vec_multiply = \ + bpy.data.node_groups[Global.NORMAL_NG_NAME].nodes.get('Vector Math') + vec_multiply.inputs[1].default_value[1] = -.5 if self.flip_y else .5 - def update_useTextureNormals(self, _context: Context) -> None: - if not self.modalState: + def update_use_texture_normals(self, _context: Context) -> None: + if not self.preview_state: 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') + tree = bpy.data.node_groups[Global.NORMAL_NG_NAME] + vec_transform = tree.nodes.get('Vector Transform') + group_output = tree.nodes.get('Group Output') - links = ng_normal.links - if self.useTextureNormals: + links = tree.links + if self.use_texture: links.new( vec_transform.inputs["Vector"], - ng_normal.nodes.get('Bevel').outputs["Normal"] + tree.nodes.get('Bevel').outputs["Normal"] ) links.new( - group_output.inputs["Output"], - ng_normal.nodes.get('Mix Shader').outputs["Shader"] + group_output.inputs["Shader"], + tree.nodes.get('Mix Shader').outputs["Shader"] ) else: links.new( vec_transform.inputs["Vector"], - ng_normal.nodes.get('Bevel.001').outputs["Normal"] + tree.nodes.get('Bevel.001').outputs["Normal"] ) links.new( - group_output.inputs["Output"], - ng_normal.nodes.get('Vector Math.001').outputs["Vector"] + group_output.inputs["Shader"], + tree.nodes.get('Vector Math.001').outputs["Vector"] ) + flip_y: BoolProperty( + name="Flip Y (-Y)", + description="Flip the normal map Y direction", + options={'SKIP_SAVE'}, + update=update_flip_y + ) + use_texture: BoolProperty( + name="Use Texture Normals", + description="Use texture normals linked to the Principled BSDF", + options={'SKIP_SAVE'}, + default=True, + update=update_use_texture_normals + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + +class GRABDOC_curvature(GRABDOC_baker_defaults, PropertyGroup): + NAME = "curvature" + ALIAS = NAME.capitalize() + SUPPORTED_ENGINES = ( + ('workbench', "Workbench", ""), + ) + def update_curvature(self, context: Context): - if self.modalState: - scene_shading = bpy.data.scenes[str( - context.scene.name)].display.shading + if not self.preview_state: + return + scene_shading = \ + bpy.data.scenes[str(context.scene.name)].display.shading + scene_shading.cavity_ridge_factor = \ + scene_shading.curvature_ridge_factor = self.ridge + scene_shading.curvature_valley_factor = self.valley + + ridge: FloatProperty( + name="", + default=2, + min=0, + max=2, + precision=3, + step=.1, + update=update_curvature, + subtype='FACTOR' + ) + valley: FloatProperty( + name="", + default=1.5, + min=0, + max=2, + precision=3, + step=.1, + update=update_curvature, + subtype='FACTOR' + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) - 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: 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 +class GRABDOC_occlusion(GRABDOC_baker_defaults, PropertyGroup): + NAME = "occlusion" + ALIAS = "Ambient Occlusion" + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) - 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_gamma(self, _context: Context): + gamma = bpy.data.node_groups[Global.AO_NG_NAME].nodes.get('Gamma') + gamma.inputs[1].default_value = self.gamma - def update_occlusion_distance(self, _context: Context): - ao = bpy.data.node_groups[Global.NG_AO_NAME].nodes.get( + def update_distance(self, _context: Context): + ao = bpy.data.node_groups[Global.AO_NG_NAME].nodes.get( 'Ambient Occlusion') - ao.inputs[1].default_value = self.distanceOcclusion + ao.inputs[1].default_value = self.distance + + gamma: FloatProperty( + default=1, + min=.001, + soft_max=10, + step=.17, + name="", + description="Intensity of AO (calculated with gamma)", + update=update_gamma + ) + distance: FloatProperty( + default=1, + min=0, + soft_max=100, + step=.03, + subtype='DISTANCE', + name="", + description="The distance AO rays travel", + update=update_distance + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + - def update_manual_height_range(self, context: Context): +class GRABDOC_height(GRABDOC_baker_defaults, PropertyGroup): + NAME = "height" + ALIAS = NAME.capitalize() + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) + + def update_method(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': - rendered_obs = get_rendered_objects(context) - set_guide_height(rendered_obs) + if not self.preview_state: + return + bpy.data.objects[Global.BG_PLANE_NAME].active_material = \ + bpy.data.materials[Global.GD_MATERIAL_NAME] + if self.method == 'AUTO': + rendered_obs = get_rendered_objects() + set_guide_height(rendered_obs) - def update_height_guide(self, context: Context): + def update_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') + bpy.data.node_groups[Global.HEIGHT_NG_NAME].nodes.get('Map Range') map_range.inputs[1].default_value = \ - gd_camera_ob_z + -self.guideHeight + gd_camera_ob_z + -self.distance map_range.inputs[2].default_value = \ gd_camera_ob_z - ramp = bpy.data.node_groups[Global.NG_HEIGHT_NAME].nodes.get( + ramp = bpy.data.node_groups[Global.HEIGHT_NG_NAME].nodes.get( 'ColorRamp') ramp.color_ramp.elements[0].color = \ - (0, 0, 0, 1) if self.invertMaskHeight else (1, 1, 1, 1) + (0, 0, 0, 1) if self.invert else (1, 1, 1, 1) ramp.color_ramp.elements[1].color = \ - (1, 1, 1, 1) if self.invertMaskHeight else (0, 0, 0, 1) + (1, 1, 1, 1) if self.invert else (0, 0, 0, 1) ramp.location = \ (-400, 0) - if self.rangeTypeHeight == 'MANUAL': + if self.method == 'MANUAL': scene_setup(self, context) # Update here so that it refreshes live in the VP - if self.modalState: - bpy.data.objects[Global.BG_PLANE_NAME].active_material = bpy.data.materials[Global.GD_MATERIAL_NAME] + if not self.preview_state: + return + bpy.data.objects[Global.BG_PLANE_NAME].active_material = \ + bpy.data.materials[Global.GD_MATERIAL_NAME] + + invert: BoolProperty( + description="Invert height mask, useful for sculpting negatively", + update=update_guide + ) + distance: FloatProperty( + name="", + default=1, + min=.01, + soft_max=100, + step=.03, + subtype='DISTANCE', + update=update_guide + ) + method: EnumProperty( + items=( + ('AUTO', "Auto", ""), + ('MANUAL', "Manual", "") + ), + update=update_method, + description="Height method, use manual if auto produces range errors" + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + +class GRABDOC_alpha(GRABDOC_baker_defaults, PropertyGroup): + NAME = "alpha" + ALIAS = NAME.capitalize() + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) def update_alpha(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_ALPHA_NAME].nodes.get( - 'Map Range') + Global.TRIM_CAMERA_NAME + ).location[2] + map_range = \ + bpy.data.node_groups[Global.ALPHA_NG_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 = \ + bpy.data.node_groups[Global.ALPHA_NG_NAME].nodes.get('Invert') + invert.inputs[0].default_value = 0 if self.invert 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 + # NOTE: Update here so that it refreshes live in the VP + if self.preview_state: + bpy.data.objects[Global.BG_PLANE_NAME].active_material = \ + bpy.data.materials[Global.GD_MATERIAL_NAME] - # Update here so that it refreshes live in the VP - if self.modalState: - bpy.data.objects[Global.BG_PLANE_NAME].active_material = bpy.data.materials[Global.GD_MATERIAL_NAME] + invert: BoolProperty( + description="Invert the Alpha mask", + update=update_alpha + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + +class GRABDOC_id(GRABDOC_baker_defaults, PropertyGroup): + NAME = "id" + ALIAS = "Material ID" + SUPPORTED_ENGINES = ( + ('workbench', "Workbench", ""), + ) + + method_list = ( + ('RANDOM', "Random", ""), + ('MATERIAL', "Material", ""), + ('VERTEX', "Object / Vertex", "") + ) + method: EnumProperty( + items=method_list, + name=f"{ALIAS} Method" + ) + ui_method: EnumProperty( + items=method_list, + default="MATERIAL" + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + +class GRABDOC_color(GRABDOC_baker_defaults, PropertyGroup): + NAME = "color" + ALIAS = "Base Color" + MARMOSET_COMPATIBLE = False + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) + + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + +class GRABDOC_roughness(GRABDOC_baker_defaults, PropertyGroup): + NAME = "roughness" + ALIAS = NAME.capitalize() + MARMOSET_COMPATIBLE = False + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) 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 + invert = \ + bpy.data.node_groups[Global.ROUGHNESS_NG_NAME].nodes.get('Invert') + invert.inputs[0].default_value = 1 if self.invert else 0 # Update here so that it refreshes live in the VP - # if self.modalState: - # bpy.data.objects[BG_PLANE_NAME].active_material = bpy.data.materials[GD_MATERIAL_NAME] + # if self.preview_state: + # bpy.data.objects[BG_PLANE_NAME].active_material = \ + # bpy.data.materials[GD_MATERIAL_NAME] - 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() + invert: BoolProperty( + description="Invert the Roughness (AKA Glossiness)", + update=update_roughness + ) + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + + + +class GRABDOC_metalness(GRABDOC_baker_defaults, PropertyGroup): + NAME = "metalness" + ALIAS = NAME.capitalize() + MARMOSET_COMPATIBLE = False + SUPPORTED_ENGINES = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) + + engine: EnumProperty( + items=SUPPORTED_ENGINES, + name='Render Engine', + update=GRABDOC_baker_defaults.update_engine + ) + +class GRABDOC_property_group(PropertyGroup): + MAP_TYPES = ( + ('none', "None", ""), + ('normals', "Normals", ""), + ('curvature', "Curvature", ""), + ('occlusion', "Ambient Occlusion", ""), + ('height', "Height", ""), + ('id', "Material ID", ""), + ('alpha', "Alpha", ""), + ('color', "Base Color", ""), + ('roughness', "Roughness", ""), + ('metalness', "Metalness", "") + ) + + def update_export_name(self, _context: Context): + if not self.export_name: + self.export_name = "untitled" 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 = '' + os.path.exists(bpy.path.abspath(self.export_path)) + if self.export_path != '' and not export_path_exists: + self.export_path = '' + + def update_res_x(self, context: Context): + if self.lock_res and self.export_res_x != self.export_res_y: + self.export_res_y = self.export_res_x + scene_setup(self, context) + + def update_res_y(self, context: Context): + if self.lock_res and self.export_res_y != self.export_res_x: + self.export_res_x = self.export_res_y + scene_setup(self, context) + + def update_scale(self, context: Context): + scene_setup(self, context) + + gd_camera_ob_z = bpy.data.objects.get( + Global.TRIM_CAMERA_NAME + ).location[2] + height_ng = bpy.data.node_groups.get(Global.HEIGHT_NG_NAME) - # PROPERTIES + map_range = height_ng.nodes.get('Map Range') + map_range.inputs[1].default_value = \ + - self.height[0].distance + gd_camera_ob_z + map_range.inputs[2].default_value = gd_camera_ob_z - # Setup settings - collSelectable: BoolProperty( + map_range_alpha = \ + bpy.data.node_groups[Global.ALPHA_NG_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 + + # Project Setup + coll_selectable: BoolProperty( update=scene_setup, - description='Sets the background plane selection capability' + description="Sets the background plane selection capability" ) - collVisible: BoolProperty(default=True, update=scene_setup, - description='Sets the visibility in the viewport') - collRendered: BoolProperty( + coll_visible: BoolProperty(default=True, update=scene_setup, + description="Sets the visibility in the viewport") + coll_rendered: BoolProperty( default=True, update=scene_setup, - description='Sets the visibility in exports, this will also enable transparency and alpha channel exports if visibility is turned off' + description=\ + "Sets the visibility in exports, this will also enable transparency and alpha channel exports if visibility is turned off" ) - scalingSet: FloatProperty( - name="Scaling Set", + scale: FloatProperty( + name="Scale", default=2, min=.1, soft_max=100, precision=3, subtype='DISTANCE', - 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 + description=\ + "Background plane & camera scale, also applies to exported plane", + update=update_scale ) - useFiltering: BoolProperty( # TODO: add to cycles + filter: 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', + description=\ + "Pixel filtering, useful for avoiding aliased edges on bake maps", update=scene_setup ) - widthFiltering: FloatProperty( + filter_width: FloatProperty( name="Filter Amount", default=1.2, min=0, soft_max=10, subtype='PIXEL', - description='The width in pixels used for filtering', + description="The width in pixels used for filtering", update=scene_setup ) - refSelection: PointerProperty( + reference: PointerProperty( name='Reference Selection', type=Image, - description='Select an image reference to use on the background plane', + description="Select an image reference to use on the background plane", update=scene_setup ) - useGrid: BoolProperty( + use_grid: BoolProperty( name='Use Grid', default=True, - description='Create a grid on the background plane for better usability while snapping', + description=\ + "Wireframe grid on plane for better snapping usability", update=scene_setup ) - gridSubdivisions: IntProperty( + grid_subdivs: IntProperty( name="Grid Subdivisions", default=0, min=0, soft_max=64, - description='The amount of subdivisions the grid will have', + description="Subdivision count for grid", update=scene_setup ) - # Baker settings - bakerType: EnumProperty( + # Baker + baker_type: EnumProperty( items=( - ('Blender', "Blender (Built-in)", "Set Baker: Blender (Built-in)"), - ('Marmoset', "Toolbag 3 & 4", "Set Baker: Marmoset Toolbag 3&4") + ('Blender', "Blender", "Set Baker: Blender"), + ('Marmoset', "Toolbag", "Set Baker: Marmoset Toolbag") ), name="Baker" ) - - exportPath: StringProperty( + export_path: StringProperty( name="Export Filepath", default=" ", description="This is the path all files will be exported to", subtype='DIR_PATH', update=update_export_path ) - - exportResX: IntProperty( + export_res_x: IntProperty( name="Res X", default=2048, min=4, soft_max=8192, update=update_res_x ) - exportResY: IntProperty( + export_res_y: IntProperty( name="Res Y", default=2048, min=4, soft_max=8192, update=update_res_y ) - - lockRes: BoolProperty( + lock_res: BoolProperty( name='Sync Resolution', default=True, update=update_res_x ) - - exportName: StringProperty( + export_name: StringProperty( name="", - description="Export name used for all exported maps. You can also add a prefix here", + description="Prefix name used for exported maps", default="untitled", update=update_export_name ) + use_bake_collections: BoolProperty( + description="Add a collection to the scene for use as bake groups", + update=scene_setup + ) + export_plane: BoolProperty( + description="Export the background plane as an unwrapped FBX" + ) - imageType: EnumProperty( + # Image Formats + format: EnumProperty( items=( ('PNG', "PNG", ""), ('TIFF', "TIFF", ""), @@ -501,34 +745,29 @@ def update_export_path(self, _context: Context): ), name="Format" ) - - # PNG/ALL - colorDepth: EnumProperty( + depth: EnumProperty( items=( ('16', "16", ""), ('8', "8", "") ) ) - - imageCompPNG: IntProperty( # Use our own property so we can assign a new default + # NOTE: Wrapper property so we can assign a new default + png_compression: IntProperty( name="", default=50, min=0, max=100, - description='Lossless Compression for smaller image sizes, but longer export times', + description=\ + "Lossless compression for lower file size but longer bake times", subtype='PERCENTAGE' ) - - # EXR - colorDepthEXR: EnumProperty( + exr_depth: EnumProperty( items=( ('16', "16", ""), ('32', "32", "") ) ) - - # TGA - colorDepthTGA: EnumProperty( + tga_depth: EnumProperty( items=( ('16', "16", ""), ('8', "8", "") @@ -536,377 +775,30 @@ def update_export_path(self, _context: Context): default='8' ) - 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" - ) - - openFolderOnExport: BoolProperty( - description="Open the folder path in your File Explorer on map export" - ) - - uiVisibilityNormals: BoolProperty(default=True) - uiVisibilityCurvature: BoolProperty(default=True) - uiVisibilityOcclusion: BoolProperty(default=True) - uiVisibilityHeight: BoolProperty(default=True) - uiVisibilityMatID: BoolProperty(default=True) - uiVisibilityAlpha: BoolProperty(default=True) - uiVisibilityAlbedo: BoolProperty(default=True) - 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_TYPES = ( - ('curvature', "Curvature", ""), - ('occlusion', "Occlusion", ""), - ('height', "Height", ""), - ('alpha', "Alpha", ""), - ('roughness', "Roughness", ""), - ('metalness', "Metalness", ""), - ) - - channel_R: EnumProperty( - items=MAP_TYPES, - default="occlusion", - name='R' - ) - channel_G: EnumProperty( - items=MAP_TYPES, - default="roughness", - name='G' - ) - channel_B: EnumProperty( - items=MAP_TYPES, - default="metalness", - name='B' - ) - 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" - ) - flipYNormals: BoolProperty( - name="Flip Y (-Y)", - description="Flip the normal map Y direction", - options={'SKIP_SAVE'}, - update=update_flip_y - ) - useTextureNormals: BoolProperty( - name="Use Texture Normals", - description="Use texture normals linked to the Principled BSDF", - options={'SKIP_SAVE'}, - default=True, - update=update_useTextureNormals - ) - suffixNormals: StringProperty( - name="", - description="The suffix of the exported bake map", - default="normal" - ) - samplesNormals: IntProperty(name="", default=128, min=1, max=512) - samplesCyclesNormals: IntProperty(name="", default=32, min=1, max=1024) - engineNormals: EnumProperty( - items=( - ('blender_eevee', "Eevee", ""), - ('cycles', "Cycles", "") - ), - default="blender_eevee", - name='Render Engine', - update=update_engine - ) - - # Curvature - exportCurvature: BoolProperty(name='Export Curvature', default=True) - ridgeCurvature: FloatProperty( - name="", - default=2, - min=0, - max=2, - precision=3, - step=.1, - update=update_curvature, - subtype='FACTOR' - ) - valleyCurvature: FloatProperty( - name="", - default=1.5, - min=0, - max=2, - precision=3, - step=.1, - update=update_curvature, - subtype='FACTOR' - ) - suffixCurvature: StringProperty( - name="", - description="The suffix of the exported bake map", - default="curvature" - ) - contrastCurvature: EnumProperty( - items=( - ('None', "None (Medium)", ""), - ('Very_High_Contrast', "Very High", ""), - ('High_Contrast', "High", ""), - ('Medium_High_Contrast', "Medium High", ""), - ('Medium_Low_Contrast', "Medium Low", ""), - ('Low_Contrast', "Low", ""), - ('Very_Low_Contrast', "Very Low", "") - ), - name="Curvature Contrast" - ) - samplesCurvature: EnumProperty( - items=( - ('OFF', "No Anti-Aliasing", ""), - ('FXAA', "1 Sample", ""), - ('5', "5 Samples", ""), - ('8', "8 Samples", ""), - ('11', "11 Samples", ""), - ('16', "16 Samples", ""), - ('32', "32 Samples", "") - ), - default="32", - name="Curvature Samples" - ) - - # Occlusion - exportOcclusion: BoolProperty(name='Export Occlusion', default=True) - reimportAsMatOcclusion: BoolProperty( - description="This will reimport the Occlusion map as a material for use in Blender" - ) - gammaOcclusion: FloatProperty( - default=1, - min=.001, - soft_max=10, - step=.17, - name="", - description="Intensity of AO (calculated with gamma)", - update=update_occlusion_gamma - ) - distanceOcclusion: FloatProperty( - default=1, - min=0, - soft_max=100, - step=.03, - subtype='DISTANCE', - name="", - description="The distance AO rays travel", - update=update_occlusion_distance - ) - suffixOcclusion: StringProperty( - name="", - description="The suffix of the exported bake map", - default="ao" - ) - samplesOcclusion: IntProperty(name="", default=128, min=1, max=512) - contrastOcclusion: EnumProperty( - items=( - ('None', "None (Medium)", ""), - ('Very_High_Contrast', "Very High", ""), - ('High_Contrast', "High", ""), - ('Medium_High_Contrast', "Medium High", ""), - ('Medium_Low_Contrast', "Medium Low", ""), - ('Low_Contrast', "Low", ""), - ('Very_Low_Contrast', "Very Low", "") - ), - name="Occlusion Contrast" - ) - - # Height - 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 - ) - rangeTypeHeight: EnumProperty( - items=( - ('AUTO', "Auto", ""), - ('MANUAL', "Manual", "") - ), - update=update_manual_height_range, - description="Automatic or manual height range. Use manual if automatic is giving you incorrect results or if baking is really slow" - ) - suffixHeight: StringProperty( - name="", - description="The suffix of the exported bake map", - default="height" - ) - samplesHeight: IntProperty(name="", default=128, min=1, max=512) - contrastHeight: EnumProperty( - items=( - ('None', "None (Medium)", ""), - ('Very_High_Contrast', "Very High", ""), - ('High_Contrast', "High", ""), - ('Medium_High_Contrast', "Medium High", ""), - ('Medium_Low_Contrast', "Medium Low", ""), - ('Low_Contrast', "Low", ""), - ('Very_Low_Contrast', "Very Low", "") - ), - name="Height Contrast" - ) - - # Alpha - exportAlpha: BoolProperty(name='Export Alpha', update=scene_setup) - invertMaskAlpha: BoolProperty( - description="Invert the Alpha mask", update=update_alpha - ) - suffixAlpha: StringProperty( - name="", - description="The suffix of the exported bake map", - default="alpha" - ) - samplesAlpha: IntProperty(name="", default=128, min=1, max=512) - - # MatID - exportMatID: BoolProperty(name='Export Material ID', default=True) - methodMatID: EnumProperty( - items=( - ('RANDOM', "Random", ""), - ('MATERIAL', "Material", ""), - ('VERTEX', "Object / Vertex", "") - ), - name='ID Method' - ) - fakeMethodMatID: EnumProperty( # Not actually used, just for UI representation - items=( - ('RANDOM', "Random", ""), - ('MATERIAL', "Material", ""), - ('VERTEX', "Object / Vertex", "") - ), - default="MATERIAL" - ) - suffixID: StringProperty( - name="", - description="The suffix of the exported bake map", - default="matID" - ) - samplesMatID: EnumProperty( - items=( - ('OFF', "No Anti-Aliasing", ""), - ('FXAA', "1 Sample", ""), - ('5', "5 Samples", ""), - ('8', "8 Samples", ""), - ('11', "11 Samples", ""), - ('16', "16 Samples", ""), - ('32', "32 Samples", "") - ), - default="OFF", - name="Mat ID Samples" - ) - - # Albedo - exportAlbedo: BoolProperty(name='Export Albedo', update=scene_setup) - suffixAlbedo: StringProperty( - name="", - description="The suffix of the exported bake map", - default="albedo" - ) - samplesAlbedo: IntProperty(name="", default=128, min=1, max=512) - samplesCyclesAlbedo: IntProperty(name="", default=32, min=1, max=1024) - engineAlbedo: EnumProperty( - items=( - ('blender_eevee', "Eevee", ""), - ('cycles', "Cycles", "") - ), - default="blender_eevee", - name='Render Engine', - update=update_engine - ) - - # Roughness - exportRoughness: BoolProperty(name='Export Roughness', update=scene_setup) - invertMaskRoughness: BoolProperty( - description="Invert the Roughess (to make Glossines)", update=update_roughness - ) - suffixRoughness: StringProperty( - name="", - description="The suffix of the exported bake map", - default="roughness" - ) - samplesRoughness: IntProperty(name="", default=128, min=1, max=512) - samplesCyclesRoughness: IntProperty(name="", default=32, min=1, max=1024) - engineRoughness: EnumProperty( - items=( - ('blender_eevee', "Eevee", ""), - ('cycles', "Cycles", "") - ), - default="blender_eevee", - name='Render Engine', - update=update_engine - ) - - # Metalness - exportMetalness: BoolProperty(name='Export Metalness', update=scene_setup) - suffixMetalness: StringProperty( - name="", - description="The suffix of the exported bake map", - default="metalness" - ) - samplesCyclesMetalness: IntProperty(name="", default=32, min=1, max=1024) - samplesMetalness: IntProperty(name="", default=128, min=1, max=512) - engineMetalness: EnumProperty( - items=( - ('blender_eevee', "Eevee", ""), - ('cycles', "Cycles", "") - ), - default="blender_eevee", - name='Render Engine', - update=update_engine - ) - - # MAP PREVIEW - - firstBakePreview: BoolProperty(default=True) - autoExitCamera: BoolProperty( - description='Whether or not the camera view will automatically be left when exiting a Map Preview' - ) - modalState: BoolProperty() - modalPreviewType: EnumProperty( - items=( - ('none', "None", ""), - ('normals', "Normals", ""), - ('curvature', "Curvature", ""), - ('occlusion', "Ambient Occlusion", ""), - ('height', "Height", ""), - ('ID', "Material ID", ""), - ('alpha', "Alpha", ""), - ('albedo', "Albedo", ""), - ('roughness', "Roughness", ""), - ('metalness', "Metalness", "") - ) - ) - - # MARMOSET BAKING - - marmoAutoBake: BoolProperty(name="Auto bake", default=True) - marmoClosePostBake: BoolProperty(name="Close after baking") - marmoSamples: EnumProperty( + # Map preview + preview_first_time: BoolProperty(default=True) + preview_auto_exit_camera: BoolProperty( + description=\ + "Automatically leave camera view when exiting a Map Preview" + ) + preview_state: BoolProperty() + preview_type: EnumProperty(items=MAP_TYPES) + + # Baking + normals: CollectionProperty(type=GRABDOC_normals) + curvature: CollectionProperty(type=GRABDOC_curvature) + occlusion: CollectionProperty(type=GRABDOC_occlusion) + height: CollectionProperty(type=GRABDOC_height) + id: CollectionProperty(type=GRABDOC_id) + alpha: CollectionProperty(type=GRABDOC_alpha) + color: CollectionProperty(type=GRABDOC_color) + roughness: CollectionProperty(type=GRABDOC_roughness) + metalness: CollectionProperty(type=GRABDOC_metalness) + + # Marmoset baking + marmoset_auto_bake: BoolProperty(name="Auto bake", default=True) + marmoset_auto_close: BoolProperty(name="Close after baking") + marmoset_samples: EnumProperty( items=( ('1', "1x", ""), ('4', "4x", ""), @@ -915,10 +807,15 @@ def update_export_path(self, _context: Context): ), default="16", name="Marmoset Samples", - description='The amount of samples rendered per pixel. 64x samples will NOT work in Marmoset 3 and will default to 16x samples' + description=\ + "Samples rendered per pixel. 64 samples is not supported in Marmoset 3 (defaults to 16 samples)" + ) + marmoset_occlusion_ray_count: IntProperty( + default=512, + min=32, + soft_max=4096 ) - marmoAORayCount: IntProperty(default=512, min=32, soft_max=4096) - imageType_marmo: EnumProperty( + marmoset_format: EnumProperty( items=( ('PNG', "PNG", ""), ('PSD', "PSD", "") @@ -926,6 +823,35 @@ def update_export_path(self, _context: Context): name="Format" ) + # Channel packing + # TODO: + # - Implement core functionality + # - Add all properties to presets + #use_pack_maps: BoolProperty( + # name='Enable Packing on Export', + # default=False + #) + #channel_R: EnumProperty( + # items=MAP_TYPES, + # default="occlusion", + # name='R' + #) + #channel_G: EnumProperty( + # items=MAP_TYPES, + # default="roughness", + # name='G' + #) + #channel_B: EnumProperty( + # items=MAP_TYPES, + # default="metalness", + # name='B' + #) + #channel_A: EnumProperty( + # items=MAP_TYPES, + # default="alpha", + # name='A' + #) + ################################## # REGISTRATION @@ -936,6 +862,15 @@ def update_export_path(self, _context: Context): GRABDOC_MT_presets, GRABDOC_PT_presets, GRABDOC_OT_add_preset, + GRABDOC_normals, + GRABDOC_curvature, + GRABDOC_occlusion, + GRABDOC_height, + GRABDOC_id, + GRABDOC_alpha, + GRABDOC_color, + GRABDOC_roughness, + GRABDOC_metalness, GRABDOC_property_group, GRABDOC_MT_addon_preferences, GRABDOC_OT_check_for_update @@ -946,20 +881,17 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - Scene.grabDoc = PointerProperty(type=GRABDOC_property_group) - Collection.is_gd_collection = BoolProperty() - Object.is_gd_object = BoolProperty() + Scene.gd = PointerProperty(type=GRABDOC_property_group) + Collection.gd_bake_collection = BoolProperty(default=False) + Object.gd_object = BoolProperty(default=False) - # Set the updaters repo + # Git release tracking updater.user = "oRazeD" updater.repo = "grabdoc" updater.current_version = VERSION - - # Initial check for repo updates updater.check_for_update_now() - if updater.update_ready: - print("There is a GrabDoc update available!") + print("GrabDoc update available!") def unregister(): diff --git a/ui.py b/ui.py index 502e640..3f522e5 100644 --- a/ui.py +++ b/ui.py @@ -42,16 +42,17 @@ def draw_header_preset(self, _context: Context): if proper_scene_setup(): # NOTE: This method already has # self but the IDE trips on it + # pylint: disable=no-value-for-parameter GRABDOC_PT_presets.draw_panel_header(self.layout) def draw(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd layout = self.layout col = layout.column(align=True) row = col.row(align=True) - row.enabled = not gd.modalState + row.enabled = not gd.preview_state row.scale_y = 1.5 row.operator( "grab_doc.setup_scene", @@ -69,19 +70,19 @@ def draw(self, context: Context): row.scale_y = .95 row.prop( - gd, "collSelectable", + gd, "coll_selectable", text="Select", - icon='RESTRICT_SELECT_OFF' if gd.collSelectable else 'RESTRICT_SELECT_ON' + icon='RESTRICT_SELECT_OFF' if gd.coll_selectable else 'RESTRICT_SELECT_ON' ) row.prop( - gd, "collVisible", + gd, "coll_visible", text="Visible", - icon='RESTRICT_VIEW_OFF' if gd.collVisible else 'RESTRICT_VIEW_ON' + icon='RESTRICT_VIEW_OFF' if gd.coll_visible else 'RESTRICT_VIEW_ON' ) row.prop( - gd, "collRendered", + gd, "coll_rendered", text="Render", - icon='RESTRICT_RENDER_OFF' if gd.collRendered else 'RESTRICT_RENDER_ON' + icon='RESTRICT_RENDER_OFF' if gd.coll_rendered else 'RESTRICT_RENDER_ON' ) box = col.box() @@ -89,24 +90,24 @@ def draw(self, context: Context): box.use_property_decorate = False col = box.column() - col.prop(gd, "scalingSet", text='Scaling', expand=True) + col.prop(gd, "scale", text='Scaling', expand=True) col.separator(factor=.5) row = col.row() - row.prop(gd, "widthFiltering", text="Filtering") + row.prop(gd, "filter_width", text="Filtering") row.separator() - row.prop(gd, "useFiltering", text="") + row.prop(gd, "filter", text="") col.separator() row = col.row() - row.enabled = not gd.modalState - row.prop(gd, "refSelection", text='Reference') + row.enabled = not gd.preview_state + row.prop(gd, "reference", text='Reference') row.operator("grab_doc.load_ref", text="", icon='FILE_FOLDER') col.separator(factor=.5) row = col.row() - row.prop(gd, "gridSubdivisions", text="Grid") + row.prop(gd, "grid_subdivs", text="Grid") row.separator() - row.prop(gd, "useGrid", text="") + row.prop(gd, "use_grid", text="") class GRABDOC_PT_export(PanelInfo, Panel): @@ -139,7 +140,7 @@ def marmoset_export_header_ui(self, layout: UILayout): row.scale_y = 1.5 row.operator( "grab_doc.bake_marmoset", - text="Bake in Marmoset" if bpy.context.scene.grabDoc.marmoAutoBake else "Open in Marmoset", + text="Bake in Marmoset" if bpy.context.scene.gd.marmoset_auto_bake else "Open in Marmoset", icon="EXPORT" ).send_type = 'open' row.operator( @@ -155,78 +156,73 @@ def marmoset_ui(self, gd, layout: UILayout): col2 = box.column() row = col2.row() - row.enabled = not gd.modalState - row.prop(gd, 'bakerType', text="Baker") + row.enabled = not gd.preview_state + row.prop(gd, 'baker_type', text="Baker") col2.separator(factor=.5) row = col2.row() row.alert = not self.export_path_exists - row.prop(gd, 'exportPath', text="Export Path") + row.prop(gd, 'export_path', text="Export Path") row.alert = False row.operator("grab_doc.open_folder", text='', icon="FOLDER_REDIRECT") col2.separator(factor=.5) - col2.prop(gd, "exportName", text="Name") + col2.prop(gd, "export_name", text="Name") col2.separator(factor=.5) row = col2.row() - 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") + row.prop(gd, "export_res_x", text='Resolution') + row.prop(gd, "export_res_y", text='') + row.prop(gd, 'lock_res', icon_only=True, icon="LOCKED" if gd.lock_res else "UNLOCKED") col2.separator(factor=.5) row = col2.row() - row.prop(gd, "imageType_marmo") + row.prop(gd, "marmoset_format") row.separator(factor=.5) row2 = row.row() - 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) + if gd.format == "OPEN_EXR": + row2.prop(gd, "exr_depth", expand=True) + elif gd.format != "TARGA" or gd.baker_type == 'Marmoset': + row2.prop(gd, "depth", expand=True) else: row2.enabled = False - row2.prop(gd, "colorDepthTGA", expand=True) + row2.prop(gd, "tga_depth", expand=True) col2.separator(factor=.5) row = col2.row(align=True) - row.prop(gd, "marmoSamples", text="Samples", expand=True) + row.prop(gd, "marmoset_samples", text="Samples", expand=True) box.use_property_split = False col = box.column(align=True) col.prop( - gd, "useBakeCollection", + gd, "use_bake_collections", text="Use Bake Group", - icon='CHECKBOX_HLT' if gd.useBakeCollection else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.use_bake_collections else 'CHECKBOX_DEHLT' ) col.prop( - gd, "exportPlane", + gd, "export_plane", text='Export Plane as FBX', - icon='CHECKBOX_HLT' if gd.exportPlane else 'CHECKBOX_DEHLT' - ) - col.prop( - gd, "openFolderOnExport", - text="Open Folder on Export", - icon='CHECKBOX_HLT' if gd.openFolderOnExport else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.export_plane else 'CHECKBOX_DEHLT' ) col = box.column(align=True) - col.prop(gd, 'marmoAutoBake', text='Bake on Import', icon='CHECKBOX_HLT' if gd.marmoAutoBake else 'CHECKBOX_DEHLT') + col.prop(gd, 'marmoset_auto_bake', text='Bake on Import', icon='CHECKBOX_HLT' if gd.marmoset_auto_bake else 'CHECKBOX_DEHLT') col = col.column(align=True) - col.enabled = True if gd.marmoAutoBake else False + col.enabled = True if gd.marmoset_auto_bake else False col.prop( gd, - 'marmoClosePostBake', + 'marmoset_auto_close', text='Close Toolbag after Baking', - icon='CHECKBOX_HLT' if gd.marmoClosePostBake else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.marmoset_auto_close else 'CHECKBOX_DEHLT' ) def blender_ui(self, gd, layout: UILayout): @@ -241,54 +237,54 @@ def blender_ui(self, gd, layout: UILayout): if is_pro_version(): row = col2.row() - row.enabled = not gd.modalState - row.prop(gd, 'bakerType', text="Baker") + row.enabled = not gd.preview_state + row.prop(gd, 'baker_type', text="Baker") col2.separator(factor=.5) row = col2.row() row.alert = not self.export_path_exists - row.prop(gd, 'exportPath', text="Export Path") + row.prop(gd, 'export_path', text="Export Path") row.alert = False row.operator("grab_doc.open_folder", text='', icon="FOLDER_REDIRECT") col2.separator(factor=.5) - col2.prop(gd, "exportName", text="Name") + col2.prop(gd, "export_name", text="Name") col2.separator(factor=.5) row = col2.row() - 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") + row.prop(gd, "export_res_x", text='Resolution') + row.prop(gd, "export_res_y", text='') + row.prop( + gd, + 'lock_res', + icon_only=True, + icon="LOCKED" if gd.lock_res else "UNLOCKED" + ) col2.separator(factor=.5) row = col2.row() - row.prop(gd, "imageType") - + row.prop(gd, "format") row.separator(factor=.5) - row2 = row.row() - 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) + if gd.format == "OPEN_EXR": + row2.prop(gd, "exr_depth", expand=True) + elif gd.format != "TARGA" or gd.baker_type == 'Marmoset': + row2.prop(gd, "depth", expand=True) else: row2.enabled = False - row2.prop(gd, "colorDepthTGA", expand=True) - + row2.prop(gd, "tga_depth", expand=True) col2.separator(factor=.5) - - if gd.imageType != "TARGA": + if gd.format != "TARGA": image_settings = bpy.context.scene.render.image_settings - row = col2.row(align=True) - if gd.imageType == "PNG": - row.prop(gd, "imageCompPNG", text="Compression") - elif gd.imageType == "OPEN_EXR": + if gd.format == "PNG": + row.prop(gd, "png_compression", text="Compression") + elif gd.format == "OPEN_EXR": row.prop(image_settings, "exr_codec", text="Codec") else: # TIFF row.prop(image_settings, "tiff_codec", text="Codec") @@ -297,31 +293,27 @@ def blender_ui(self, gd, layout: UILayout): col = box.column(align=True) col.prop( - gd, "useBakeCollection", + gd, "use_bake_collections", text="Use Bake Group", - icon='CHECKBOX_HLT' if gd.useBakeCollection else 'CHECKBOX_DEHLT' - ) - col.prop( - gd, "openFolderOnExport", - text="Open Folder on Export", - icon='CHECKBOX_HLT' if gd.openFolderOnExport else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.use_bake_collections else 'CHECKBOX_DEHLT' ) col.prop( - gd, "exportPlane", + gd, "export_plane", text='Export Plane as FBX', - icon='CHECKBOX_HLT' if gd.exportPlane else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.export_plane else 'CHECKBOX_DEHLT' ) def draw(self, context: Context): - gd = context.scene.grabDoc - self.export_path_exists = os.path.exists(bpy.path.abspath(gd.exportPath)) + gd = context.scene.gd + self.export_path_exists = \ + os.path.exists(bpy.path.abspath(gd.export_path)) layout = self.layout layout.activate_init = True layout.use_property_split = True layout.use_property_decorate = False - if gd.bakerType == 'Blender': + if gd.baker_type == 'Blender': self.blender_ui(gd, layout) elif is_pro_version(): # Marmoset self.marmoset_export_header_ui(layout) @@ -329,30 +321,48 @@ def draw(self, context: Context): class GRABDOC_OT_config_maps(Operator): - """Configure the UI visibility of maps, also disabling them from baking when hidden""" + """Configure bake map UI visibility, will also disable baking""" 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'} def invoke(self, context: Context, _event: Event): return context.window_manager.invoke_props_dialog(self, width = 200) def draw(self, context: Context): - gd = context.scene.grabDoc - + gd = context.scene.gd layout = self.layout - 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)") + col = layout.column(align=True) + col.prop( + gd.normals[0], 'ui_visibility', text="Normals", icon="WORLD" + ) + col.prop( + gd.curvature[0], 'ui_visibility', text="Curvature", icon="WORLD" + ) + col.prop( + gd.occlusion[0], 'ui_visibility', text="Ambient Occlusion", icon="WORLD" + ) + col.prop( + gd.height[0], 'ui_visibility', text="Height", icon="WORLD" + ) + col.prop( + gd.id[0], 'ui_visibility', text="Material ID", icon="WORLD" + ) + col.prop( + gd.alpha[0], 'ui_visibility', text="Alpha", icon="WORLD" + ) + col.prop( + gd.color[0], 'ui_visibility', text="Base Color", icon="BLENDER" + ) + col.prop( + gd.roughness[0], 'ui_visibility', text="Roughness", icon="BLENDER" + ) + col.prop( + gd.metalness[0], 'ui_visibility', text="Metalness", icon="BLENDER" + ) class GRABDOC_PT_view_edit_maps(PanelInfo, Panel): @@ -364,10 +374,15 @@ def poll(cls, _context: Context) -> bool: return proper_scene_setup() def draw_header_preset(self, _context: Context): - self.layout.operator("grab_doc.config_maps", emboss=False, text="", icon="SETTINGS") + self.layout.operator( + "grab_doc.config_maps", + emboss=False, + text="", + icon="SETTINGS" + ) def draw(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd layout = self.layout col = layout.column(align=True) @@ -383,12 +398,12 @@ def draw(self, context: Context): col.prop( gd, - 'autoExitCamera', + 'preview_auto_exit_camera', text="Leave Cam on Preview Exit", - icon='CHECKBOX_HLT' if gd.autoExitCamera else 'CHECKBOX_DEHLT' + icon='CHECKBOX_HLT' if gd.preview_auto_exit_camera else 'CHECKBOX_DEHLT' ) - if not gd.modalState: + if not gd.preview_state: return col.separator() @@ -399,27 +414,37 @@ def draw(self, context: Context): row = col.row(align=True) row.scale_y = 1.1 - 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 gd.modalPreviewType == 'normals': - GRABDOC_PT_normals_settings.draw(self, context) - elif gd.modalPreviewType == 'curvature': - GRABDOC_PT_curvature_settings.draw(self, context) - elif gd.modalPreviewType == 'occlusion': - GRABDOC_PT_occlusion_settings.draw(self, context) - elif gd.modalPreviewType == 'height': - GRABDOC_PT_height_settings.draw(self, context) - elif gd.modalPreviewType == 'ID': - GRABDOC_PT_id_settings.draw(self, context) - elif gd.modalPreviewType == 'alpha': - GRABDOC_PT_alpha_settings.draw(self, context) - elif gd.modalPreviewType == 'albedo': - GRABDOC_PT_albedo_settings.draw(self, context) - elif gd.modalPreviewType == 'roughness': - GRABDOC_PT_roughness_settings.draw(self, context) - elif gd.modalPreviewType == 'metalness': - GRABDOC_PT_metalness_settings.draw(self, context) + mat_preview_type = \ + "Material ID" if gd.preview_type == 'ID' else gd.preview_type.capitalize() + row.operator( + "grab_doc.export_preview", + text=f"Export {mat_preview_type}", + icon="EXPORT" + ) + + # TODO: May be able to circumvent if elif chain by + # just grabbing the CollectionProperty directly + # and running draw + #print(gd.get()) + + if gd.preview_type == 'normals': + GRABDOC_PT_normals.draw(self, context) + elif gd.preview_type == 'curvature': + GRABDOC_PT_curvature.draw(self, context) + elif gd.preview_type == 'occlusion': + GRABDOC_PT_occlusion.draw(self, context) + elif gd.preview_type == 'height': + GRABDOC_PT_height.draw(self, context) + elif gd.preview_type == 'ID': + GRABDOC_PT_id.draw(self, context) + elif gd.preview_type == 'alpha': + GRABDOC_PT_alpha.draw(self, context) + elif gd.preview_type == 'color': + GRABDOC_PT_color.draw(self, context) + elif gd.preview_type == 'roughness': + GRABDOC_PT_roughness.draw(self, context) + elif gd.preview_type == 'metalness': + GRABDOC_PT_metalness.draw(self, context) #class GRABDOC_PT_pack_maps(PanelInfo, Panel): @@ -428,7 +453,7 @@ def draw(self, context: Context): # @classmethod # def poll(cls, context: Context) -> bool: -# return proper_scene_setup() and not context.scene.grabDoc.modalState +# return proper_scene_setup() and not context.scene.gd.preview_state # def draw_header_preset(self, _context: Context): # self.layout.operator( @@ -439,278 +464,213 @@ def draw(self, context: Context): # ) # def draw_header(self, context: Context): -# gd = context.scene.grabDoc +# gd = context.scene.gd # # row = self.layout.row(align=True) -# row.prop(gd, 'packMaps', text='') +# row.prop(gd, '_use_pack_maps', text='') # row.separator(factor=.5) # def draw(self, context: Context): -# gd = context.scene.grabDoc +# gd = context.scene.gd # layout = self.layout # layout.use_property_split = True # layout.use_property_decorate = False # col = layout.column(align=True) -# col.prop(gd, 'channel_R') -# col.prop(gd, 'channel_G') +# 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, Panel): +class GRABDOC_PT_baker_default(PanelInfo, SubPanelInfo, Panel): + NAME = None + @classmethod def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityNormals + gd = context.scene.gd + try: + baker = getattr(gd, cls.NAME)[0] + except AttributeError: + return False + return not gd.preview_state and baker.ui_visibility def draw_header(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd + + try: + baker = getattr(gd, self.NAME)[0] + except AttributeError: + return row = self.layout.row(align=True) row.separator(factor=.5) - row.prop(gd, 'exportNormals', text="") - + row.prop(baker, 'enabled', text="") row.operator( - "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", - text="Normals Preview" - ).map_types = 'normals' - + "grab_doc.preview_map", + text=f"{self.ALIAS} Preview" + ).map_types = self.NAME row.operator( "grab_doc.offline_render", text="", icon="RENDER_STILL" - ).map_types = 'normals' + ).map_types = self.NAME row.separator(factor=1.3) + def draw_custom_properties(self, context: Context): + pass + def draw(self, context: Context): - gd = context.scene.grabDoc + gd = context.scene.gd + + try: + baker = getattr(gd, self.NAME)[0] + except AttributeError: + return layout = self.layout layout.use_property_split = True layout.use_property_decorate = False + # Core col = layout.column() - col.prop(gd, "engineNormals", text="Engine") + if len(baker.SUPPORTED_ENGINES) > 1: + col.prop(baker, 'engine', text="Engine") - col = layout.column() - col.prop(gd, 'flipYNormals', text="Flip Y (-Y)") + self.draw_custom_properties(context) - if gd.bakerType == 'Blender': - col.separator(factor=.5) - col.prop(gd, 'useTextureNormals', text="Texture Normals") + # Baker + if gd.baker_type == 'Blender': col.separator(factor=.5) - col.prop(gd, 'reimportAsMatNormals', text="Import as Material") - - col.separator(factor=1.5) - col.prop( - gd, - "samplesNormals" if gd.engineNormals == 'blender_eevee' else "samplesCyclesNormals", - text='Samples' - ) - + col.prop(baker, 'reimport', text="Re-import") + if "BLENDER_EEVEE" in baker.SUPPORTED_ENGINES \ + or "CYCLES" in baker.SUPPORTED_ENGINES: + col.separator(factor=1.5) + if baker.engine == 'blender_eevee': + prop = "samples" + else: + prop = "samples_cycles" + col.prop( + baker, + prop, + text='Samples' + ) + + # Misc col.separator(factor=.5) - col.prop(gd, 'suffixNormals', text="Suffix") - + col.prop(baker, 'suffix', text="Suffix") -class GRABDOC_PT_curvature_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityCurvature - def draw_header(self, context: Context): - gd = context.scene.grabDoc +class GRABDOC_PT_normals(GRABDOC_PT_baker_default): + NAME = "normals" + ALIAS = NAME.capitalize() - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportCurvature', text="") - - row.operator( - "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.separator(factor=1.3) - - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() + col.prop(baker, 'flip_y', text="Flip Y (-Y)") - if gd.bakerType == 'Blender': - col.prop(gd, 'ridgeCurvature', text="Ridge") - col.separator(factor=.5) - col.prop(gd, 'valleyCurvature', text="Valley") - col.separator(factor=1.5) - col.prop(gd, "samplesCurvature", text="Samples") + if gd.baker_type == 'Blender': col.separator(factor=.5) - col.prop(gd, 'contrastCurvature', text="Contrast") - - col.separator(factor=.5) - col.prop(gd, 'suffixCurvature', text="Suffix") - - -class GRABDOC_PT_occlusion_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityOcclusion + col.prop(baker, 'use_texture', text="Texture Normals") - def draw_header(self, context: Context): - gd = context.scene.grabDoc - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportOcclusion', text="") +class GRABDOC_PT_curvature(GRABDOC_PT_baker_default): + NAME = "curvature" + ALIAS = NAME.capitalize() - row.operator( - "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.separator(factor=1.3) + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] - def draw(self, context: Context): - gd = context.scene.grabDoc + if gd.baker_type != 'Blender': + return layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() + col.prop(baker, 'ridge', text="Ridge") + col.separator(factor=.5) + col.prop(baker, 'valley', text="Valley") - if gd.bakerType == 'Marmoset': - col.prop(gd, "marmoAORayCount", text="Ray Count") - - else: # Blender - col.prop(gd, 'reimportAsMatOcclusion', text="Import as Material") - col.separator(factor=.5) - col.prop(gd, 'gammaOcclusion', text="Intensity") - col.separator(factor=.5) - col.prop(gd, 'distanceOcclusion', text="Distance") - col.separator(factor=1.5) - col.prop(gd, "samplesOcclusion", text="Samples") - col.separator(factor=.5) - col.prop(gd, 'contrastOcclusion', text="Contrast") +class GRABDOC_PT_occlusion(GRABDOC_PT_baker_default): + NAME = "occlusion" + ALIAS = NAME.capitalize() - col.separator(factor=.5) - col.prop(gd, 'suffixOcclusion', text="Suffix") + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False -class GRABDOC_PT_height_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityHeight + col = layout.column() - def draw_header(self, context: Context): - gd = context.scene.grabDoc + if gd.baker_type == 'Marmoset': + col.prop(gd, "marmoset_occlusion_ray_count", text="Ray Count") + return - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportHeight', text="") + col.prop(baker, 'gamma', text="Intensity") + col.separator(factor=.5) + col.prop(baker, 'distance', text="Distance") - row.operator( - "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.separator(factor=1.3) +class GRABDOC_PT_height(GRABDOC_PT_baker_default): + NAME = "height" + ALIAS = NAME.capitalize() - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() - - if gd.bakerType == 'Blender': - col.prop(gd, 'invertMaskHeight', text="Invert Mask") + if gd.baker_type == 'Blender': + col.prop(baker, 'invert', text="Invert Mask") col.separator(factor=.5) row = col.row() - row.prop(gd, "rangeTypeHeight", text='Height Mode', expand=True) - - if gd.rangeTypeHeight == 'MANUAL': + row.prop(baker, 'method', text="Height Mode", expand=True) + if baker.method == 'MANUAL': col.separator(factor=.5) - col.prop(gd, 'guideHeight', text="0-1 Range") - - if gd.bakerType == 'Blender': - col.separator(factor=1.5) - col.prop(gd, "samplesHeight", text="Samples") - col.separator(factor=.5) - col.prop(gd, 'contrastHeight', text="Contrast") - - col.separator(factor=.5) - col.prop(gd, 'suffixHeight', text="Suffix") - - -class GRABDOC_PT_id_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityMatID + col.prop(baker, 'distance', text="0-1 Range") - def draw_header(self, context: Context): - gd = context.scene.grabDoc - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportMatID', text="") +class GRABDOC_PT_id(GRABDOC_PT_baker_default): + NAME = "id" + ALIAS = NAME.capitalize() - row.operator( - "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.separator(factor=1.3) - - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() - row = col.row() - if gd.bakerType == 'Marmoset': + if gd.baker_type == 'Marmoset': row.enabled = False - row.prop(gd, "fakeMethodMatID", text="ID Method") + row.prop(baker, 'ui_method', text="ID Method") else: - row.prop(gd, "methodMatID", text="ID Method") + row.prop(baker, 'method', text="ID Method") - if gd.methodMatID == "MATERIAL" or gd.bakerType == 'Marmoset': + if baker.method == "MATERIAL" or gd.baker_type == 'Marmoset': col = layout.column(align=True) col.separator(factor=.5) col.scale_y = 1.1 @@ -719,7 +679,10 @@ def draw(self, context: 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 = Global.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) @@ -729,87 +692,37 @@ def draw(self, context: 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 = Global.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 gd.bakerType == 'Blender': - col.separator(factor=1.5) - col.prop(gd, "samplesMatID", text="Samples") - - col.separator(factor=.5) - col.prop(gd, 'suffixID', text="Suffix") - -class GRABDOC_PT_alpha_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - def poll(cls, context: Context) -> bool: - gd = context.scene.grabDoc - return not gd.modalState and gd.uiVisibilityAlpha +class GRABDOC_PT_alpha(GRABDOC_PT_baker_default): + NAME = "alpha" + ALIAS = NAME.capitalize() - def draw_header(self, context: Context): - gd = context.scene.grabDoc - - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportAlpha', text="") - - row.operator( - "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.separator(factor=1.3) - - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column() + if gd.baker_type == 'Blender': + col.prop(baker, 'invert', text="Invert Mask") - if gd.bakerType == 'Blender': - col.prop(gd, 'invertMaskAlpha', text="Invert Mask") - col.separator(factor=1.5) - col.prop(gd, 'samplesAlpha', text="Samples") - - col.separator(factor=.5) - col.prop(gd, 'suffixAlpha', text="Suffix") - - -class GRABDOC_PT_albedo_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - 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: Context): - gd = context.scene.grabDoc - - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportAlbedo', text="") - row.operator( - "grab_doc.preview_warning" if gd.firstBakePreview else "grab_doc.preview_map", - text="Albedo Preview" - ).map_types = 'albedo' +class GRABDOC_PT_color(GRABDOC_PT_baker_default): + NAME = "color" + ALIAS = NAME.capitalize() - row.operator( - "grab_doc.offline_render", - text="", - icon="RENDER_STILL" - ).map_types = 'albedo' - row.separator(factor=1.3) - - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + _baker = getattr(gd, self.NAME)[0] layout = self.layout @@ -818,47 +731,14 @@ def draw(self, context: Context): layout.use_property_split = True layout.use_property_decorate = False - col = layout.column() - col.prop(gd, "engineAlbedo", text="Engine") - - col.separator(factor=1.5) - col.prop( - gd, - "samplesAlbedo" if gd.engineAlbedo == 'blender_eevee' else "samplesCyclesAlbedo", - text='Samples' - ) - - col.separator(factor=.5) - col.prop(gd, 'suffixAlbedo', text="Suffix") - - -class GRABDOC_PT_roughness_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - 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: Context): - gd = context.scene.grabDoc - - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportRoughness', text="") - row.operator( - "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.separator(factor=1.3) +class GRABDOC_PT_roughness(GRABDOC_PT_baker_default): + NAME = "roughness" + ALIAS = NAME.capitalize() - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + baker = getattr(gd, self.NAME)[0] layout = self.layout @@ -868,51 +748,19 @@ def draw(self, context: Context): layout.use_property_decorate = False col = layout.column() - col.prop(gd, "engineRoughness", text="Engine") - col.separator(factor=.5) - if gd.bakerType == 'Blender': - col.prop(gd, 'invertMaskRoughness', text="Invert") + if gd.baker_type == 'Blender': + col.prop(baker, 'invert', text="Invert") col.separator(factor=.5) - col.separator(factor=1.5) - col.prop( - gd, - "samplesRoughness" if gd.engineRoughness == 'blender_eevee' else "samplesCyclesRoughness", - text='Samples' - ) - - col.separator(factor=.5) - col.prop(gd, 'suffixRoughness', text="Suffix") - - -class GRABDOC_PT_metalness_settings(PanelInfo, SubPanelInfo, Panel): - @classmethod - 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: Context): - gd = context.scene.grabDoc - - row = self.layout.row(align=True) - row.separator(factor=.5) - row.prop(gd, 'exportMetalness', text="") - - row.operator( - "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.separator(factor=1.3) +class GRABDOC_PT_metalness(GRABDOC_PT_baker_default): + NAME = "metalness" + ALIAS = NAME.capitalize() - def draw(self, context: Context): - gd = context.scene.grabDoc + def draw_custom_properties(self, context: Context): + gd = context.scene.gd + _baker = getattr(gd, self.NAME)[0] layout = self.layout @@ -921,19 +769,6 @@ def draw(self, context: Context): layout.use_property_split = True layout.use_property_decorate = False - col = layout.column() - col.prop(gd, "engineMetalness", text="Engine") - - col.separator(factor=1.5) - col.prop( - gd, - "samplesMetalness" if gd.engineMetalness == 'blender_eevee' else "samplesCyclesMetalness", - text='Samples' - ) - - col.separator(factor=.5) - col.prop(gd, 'suffixMetalness', text="Suffix") - ################################################ # REGISTRATION @@ -945,16 +780,16 @@ def draw(self, context: Context): GRABDOC_OT_config_maps, GRABDOC_PT_export, GRABDOC_PT_view_edit_maps, - #GRABDOC_PT_pack_maps, - GRABDOC_PT_normals_settings, - GRABDOC_PT_curvature_settings, - GRABDOC_PT_occlusion_settings, - GRABDOC_PT_height_settings, - GRABDOC_PT_id_settings, - GRABDOC_PT_alpha_settings, - GRABDOC_PT_albedo_settings, - GRABDOC_PT_roughness_settings, - GRABDOC_PT_metalness_settings + GRABDOC_PT_normals, + GRABDOC_PT_curvature, + GRABDOC_PT_occlusion, + GRABDOC_PT_height, + GRABDOC_PT_id, + GRABDOC_PT_alpha, + GRABDOC_PT_color, + GRABDOC_PT_roughness, + GRABDOC_PT_metalness + # GRABDOC_PT_pack_maps ) diff --git a/utils/baker.py b/utils/baker.py index ceaeff3..5797e71 100644 --- a/utils/baker.py +++ b/utils/baker.py @@ -1,13 +1,14 @@ import os +from typing import Iterable import bpy -from bpy.types import Context +from bpy.types import Context, Object 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 +from .generic import get_format +from .render import set_guide_height, get_rendered_objects ################################################ @@ -45,7 +46,7 @@ def set_color_management(display_device: str='None') -> None: def export_and_preview_setup(self, context: Context): scene = context.scene - gd = scene.grabDoc + gd = scene.gd render = scene.render # TODO: Preserve use_local_camera & original camera @@ -122,7 +123,7 @@ def export_and_preview_setup(self, context: Context): # Save & Set - Film self.savedFilterSize = render.filter_size - render.filter_size = gd.widthFiltering + render.filter_size = gd.filter_width self.savedFilterSizeCycles = context.scene.cycles.filter_width self.savedFilterSizeTypeCycles = context.scene.cycles.pixel_filter_type @@ -134,8 +135,8 @@ def export_and_preview_setup(self, context: Context): image_settings = render.image_settings # Set - Dimensions (Don't bother saving these) - render.resolution_x = gd.exportResX - render.resolution_y = gd.exportResY + render.resolution_x = gd.export_res_x + render.resolution_y = gd.export_res_y render.resolution_percentage = 100 # Save & Set - Output @@ -143,22 +144,22 @@ def export_and_preview_setup(self, context: Context): self.savedFileFormat = image_settings.file_format # If background plane not visible in render, create alpha channel - if not gd.collRendered: + if not gd.coll_rendered: render.film_transparent = True image_settings.color_mode = 'RGBA' else: image_settings.color_mode = 'RGB' - image_settings.file_format = gd.imageType + image_settings.file_format = gd.format - if gd.imageType == 'OPEN_EXR': - image_settings.color_depth = gd.colorDepthEXR - elif gd.imageType != 'TARGA': - image_settings.color_depth = gd.colorDepth + if gd.format == 'OPEN_EXR': + image_settings.color_depth = gd.exr_depth + elif gd.format != 'TARGA': + image_settings.color_depth = gd.depth - if gd.imageType == "PNG": - image_settings.compression = gd.imageCompPNG + if gd.format == "PNG": + image_settings.compression = gd.png_compression self.savedColorDepth = image_settings.color_depth @@ -193,20 +194,20 @@ def export_and_preview_setup(self, context: Context): ## PLANE REFERENCE ## - self.savedRefSelection = gd.refSelection.name if gd.refSelection else None + self.savedRefSelection = gd.reference.name if gd.reference else None ## OBJECT VISIBILITY ## bg_plane = bpy.data.objects.get(Global.BG_PLANE_NAME) - bg_plane.hide_viewport = not gd.collVisible - bg_plane.hide_render = not gd.collRendered + bg_plane.hide_viewport = not gd.coll_visible + bg_plane.hide_render = not gd.coll_rendered bg_plane.hide_set(False) def export_refresh(self, context: Context) -> None: scene = context.scene - gd = scene.grabDoc + gd = scene.gd render = scene.render ## VIEW LAYER PROPERTIES ## @@ -289,49 +290,61 @@ def export_refresh(self, context: Context) -> None: ## PLANE REFERENCE ## if self.savedRefSelection: - gd.refSelection = bpy.data.images[self.savedRefSelection] + gd.reference = bpy.data.images[self.savedRefSelection] -def reimport_as_material(suffix) -> None: - """Reimport an exported map as a material for further use inside of Blender""" - gd = bpy.context.scene.grabDoc +def get_baked_maps() -> dict: + pass - mat_name = f'{gd.exportName}_{suffix}' - # 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: - bpy.data.materials.remove(bpy.data.materials.get(mat_name)) - - # Remove original image - if mat_name in bpy.data.images: - bpy.data.images.remove(bpy.data.images.get(mat_name)) +# TODO: OK this is a stupid function +# +# Lets just create a grabdoc render result materials and assign matching texture names to each slot. Think of it like how Marmoset has a bake result shader. This will increase initial shader complexity but will avoid needing to make a material for every baked image +def reimport_as_material(suffix, map_names: list) -> None: + """Reimport baked textures as a material for further use inside of Blender""" + gd = bpy.context.scene.gd # Create material - mat = bpy.data.materials.new(name=mat_name) + mat = bpy.data.materials.get(Global.REIMPORT_MAT_NAME) + if mat is None: + mat = bpy.data.materials.new(Global.REIMPORT_MAT_NAME) mat.use_nodes = True + links = mat.node_tree.links + + # Create nodes + bsdf = mat.node_tree.nodes['Principled BSDF'] + + # Import images + # TODO: Create function for getting enabled maps + y_offset = 0 + for name in map_names: + image_name = Global.GD_PREFIX + name + if image_name in bpy.data.images: + bpy.data.images.remove(bpy.data.images.get(image_name)) + + image = mat.node_tree.nodes.get(image_name) + if image is None: + image = mat.node_tree.nodes.new('ShaderNodeTexImage') + image.name = image_name + image.location = (-300, y_offset) + y_offset -= 200 + + export_name = f'{gd.export_name}_{suffix}' + export_path = os.path.join( + bpy.path.abspath(gd.export_path), export_name + get_format() + ) + if not os.path.exists(export_path): + continue + image.image = bpy.data.images.load(export_path) - 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 = 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.image.colorspace_settings.name = 'Non-Color' + if name not in ("color"): + image.image.colorspace_settings.name = 'Non-Color' - # Make links - mat.node_tree.links.new(output.inputs['Surface'], image.outputs['Color']) + # Link image to matching bsdf socket + try: + links.new(bsdf.inputs[name], image.outputs[name]) + except KeyError: + pass ################################################ @@ -340,33 +353,33 @@ def reimport_as_material(suffix) -> None: # NORMALS -def normals_setup(self, context: Context) -> None: - scene = context.scene +def normals_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene render = scene.render - if scene.grabDoc.engineNormals == 'blender_eevee': + if scene.gd.normals[0].engine == 'blender_eevee': scene.eevee.taa_render_samples = \ - scene.eevee.taa_samples = scene.grabDoc.samplesNormals + scene.eevee.taa_samples = scene.gd.normals[0].samples else: # Cycles scene.cycles.samples = \ - scene.cycles.preview_samples = scene.grabDoc.samplesCyclesNormals + scene.cycles.preview_samples = scene.gd.normals[0].samples_cycles - render.engine = str(scene.grabDoc.engineNormals).upper() + render.engine = str(scene.gd.normals[0].engine).upper() set_color_management('None') - ng_normal = bpy.data.node_groups[Global.NG_NORMAL_NAME] + ng_normal = bpy.data.node_groups[Global.NORMAL_NG_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: + if scene.gd.normals[0].use_texture: links.new( vec_transform.inputs["Vector"], ng_normal.nodes.get('Bevel').outputs["Normal"] ) links.new( - group_output.inputs["Output"], + group_output.inputs["Shader"], ng_normal.nodes.get('Mix Shader').outputs["Shader"] ) else: @@ -375,28 +388,28 @@ def normals_setup(self, context: Context) -> None: ng_normal.nodes.get('Bevel.001').outputs["Normal"] ) links.new( - group_output.inputs["Output"], + group_output.inputs["Shader"], ng_normal.nodes.get('Vector Math.001').outputs["Vector"] ) - add_ng_to_mat(self, context, setup_type=Global.NG_NORMAL_NAME) + add_ng_to_mat(self, name=Global.NORMAL_NG_NAME, objects=objects) # CURVATURE -def curvature_setup(self, context: Context) -> None: - scene = context.scene - gd = scene.grabDoc +def curvature_setup(self) -> None: + scene = bpy.context.scene + gd = scene.gd 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 = gd.samplesCurvature + scene.display.render_aa = scene.display.viewport_aa = gd.curvature[0].samples_workbench scene_shading.light = 'FLAT' scene_shading.color_type = 'SINGLE' set_color_management('sRGB') try: - scene.view_settings.look = gd.contrastCurvature.replace('_', ' ') + scene.view_settings.look = gd.curvature[0].contrast.replace('_', ' ') except TypeError: pass @@ -413,16 +426,17 @@ def curvature_setup(self, context: Context) -> None: scene_shading.show_cavity = True scene_shading.cavity_type = 'BOTH' scene_shading.cavity_ridge_factor = \ - scene_shading.curvature_ridge_factor = gd.ridgeCurvature - scene_shading.curvature_valley_factor = gd.valleyCurvature + scene_shading.curvature_ridge_factor = gd.curvature[0].ridge + scene_shading.curvature_valley_factor = gd.curvature[0].valley scene_shading.cavity_valley_factor = 0 scene_shading.single_color = (.214041, .214041, .214041) scene.display.matcap_ssao_distance = .075 -def curvature_refresh(self, context: Context) -> None: - scene_shading = bpy.data.scenes[str(context.scene.name)].display.shading +def curvature_refresh(self) -> None: + scene_shading = \ + bpy.data.scenes[str(bpy.context.scene.name)].display.shading scene_shading.cavity_ridge_factor = self.savedCavityRidgeFactor scene_shading.curvature_ridge_factor = self.savedCurveRidgeFactor @@ -432,23 +446,23 @@ def curvature_refresh(self, context: Context) -> None: scene_shading.cavity_type = self.savedCavityType scene_shading.show_cavity = self.savedCavity - context.scene.display.matcap_ssao_distance = self.savedRidgeDistance + bpy.context.scene.display.matcap_ssao_distance = self.savedRidgeDistance bpy.data.objects[Global.BG_PLANE_NAME].color[3] = 1 # AMBIENT OCCLUSION -def occlusion_setup(self, context: Context) -> None: - scene = context.scene - gd = scene.grabDoc +def occlusion_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene + gd = scene.gd eevee = scene.eevee scene.render.engine = 'BLENDER_EEVEE' - eevee.taa_render_samples = eevee.taa_samples = gd.samplesOcclusion + eevee.taa_render_samples = eevee.taa_samples = gd.occlusion[0].samples set_color_management('None') try: - scene.view_settings.look = gd.contrastOcclusion.replace('_', ' ') + scene.view_settings.look = gd.occlusion[0].contrast.replace('_', ' ') except TypeError: pass @@ -462,11 +476,11 @@ def occlusion_setup(self, context: Context) -> None: # Set - Ambient Occlusion eevee.use_gtao = True - add_ng_to_mat(self, context, setup_type=Global.NG_AO_NAME) + add_ng_to_mat(self, name=Global.AO_NG_NAME, objects=objects) -def occlusion_refresh(self, context: Context) -> None: - eevee = context.scene.eevee +def occlusion_refresh(self) -> None: + eevee = bpy.context.scene.eevee eevee.use_overscan = self.savedUseOverscan eevee.overscan_size = self.savedOverscanSize @@ -477,109 +491,110 @@ def occlusion_refresh(self, context: Context) -> None: # HEIGHT -def height_setup(self, context: Context) -> None: - scene = context.scene - gd = scene.grabDoc +def height_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene + gd = scene.gd scene.render.engine = 'BLENDER_EEVEE' - scene.eevee.taa_render_samples = scene.eevee.taa_samples = gd.samplesHeight + scene.eevee.taa_render_samples = scene.eevee.taa_samples = gd.height[0].samples set_color_management('None') try: - scene.view_settings.look = gd.contrastHeight.replace('_', ' ') + scene.view_settings.look = gd.height[0].contrast.replace('_', ' ') except TypeError: pass - add_ng_to_mat(self, context, setup_type=Global.NG_HEIGHT_NAME) + add_ng_to_mat(self, name=Global.HEIGHT_NG_NAME, objects=objects) - if gd.rangeTypeHeight == 'AUTO': - set_guide_height() + if gd.height[0].method == 'AUTO': + rendered_obs = get_rendered_objects() + set_guide_height(rendered_obs) # MATERIAL ID -def id_setup(_self, context: Context) -> None: - scene = context.scene - gd = scene.grabDoc +def id_setup() -> None: + scene = bpy.context.scene + gd = scene.gd 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 = gd.samplesMatID + scene.display.render_aa = scene.display.viewport_aa = gd.id[0].samples_workbench scene_shading.light = 'FLAT' set_color_management('sRGB') # Choose the method of ID creation based on user preference - scene_shading.color_type = gd.methodMatID + scene_shading.color_type = gd.id[0].method # ALPHA -def alpha_setup(self, context: Context) -> None: - scene = context.scene +def alpha_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene render = scene.render render.engine = 'BLENDER_EEVEE' scene.eevee.taa_render_samples = \ - scene.eevee.taa_samples = scene.grabDoc.samplesAlpha + scene.eevee.taa_samples = scene.gd.alpha[0].samples set_color_management('None') - add_ng_to_mat(self, context, setup_type=Global.NG_ALPHA_NAME) + add_ng_to_mat(self, name=Global.ALPHA_NG_NAME, objects=objects) -# ALBEDO -def albedo_setup(self, context: Context) -> None: - scene = context.scene +# COLOR +def color_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene render = scene.render - if scene.grabDoc.engineAlbedo == 'blender_eevee': + if scene.gd.color[0].engine == 'blender_eevee': scene.eevee.taa_render_samples = \ - scene.eevee.taa_samples = scene.grabDoc.samplesAlbedo + scene.eevee.taa_samples = scene.gd.color[0].samples else: # Cycles scene.cycles.samples = \ - scene.cycles.preview_samples = scene.grabDoc.samplesCyclesAlbedo + scene.cycles.preview_samples = scene.gd.color[0].samples_cycles - render.engine = str(scene.grabDoc.engineAlbedo).upper() + render.engine = str(scene.gd.color[0].engine).upper() set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=Global.NG_ALBEDO_NAME) + add_ng_to_mat(self, name=Global.COLOR_NG_NAME, objects=objects) # ROUGHNESS -def roughness_setup(self, context: Context) -> None: - scene = context.scene +def roughness_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene render = scene.render - if scene.grabDoc.engineRoughness == 'blender_eevee': + if scene.gd.roughness[0].engine == 'blender_eevee': scene.eevee.taa_render_samples = \ - scene.eevee.taa_samples = scene.grabDoc.samplesRoughness + scene.eevee.taa_samples = scene.gd.roughness[0].samples else: # Cycles scene.cycles.samples = \ - scene.cycles.preview_samples = scene.grabDoc.samplesCyclesRoughness + scene.cycles.preview_samples = scene.gd.roughness[0].samples_cycles - render.engine = str(scene.grabDoc.engineRoughness).upper() + render.engine = str(scene.gd.roughness[0].engine).upper() set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=Global.NG_ROUGHNESS_NAME) + add_ng_to_mat(self, name=Global.ROUGHNESS_NG_NAME, objects=objects) # METALNESS -def metalness_setup(self, context: Context) -> None: - scene = context.scene +def metalness_setup(self, objects: Iterable[Object]) -> None: + scene = bpy.context.scene render = scene.render - if scene.grabDoc.engineMetalness == 'blender_eevee': + if scene.gd.metalness[0].engine == 'blender_eevee': scene.eevee.taa_render_samples = \ - scene.eevee.taa_samples = scene.grabDoc.samplesMetalness + scene.eevee.taa_samples = scene.gd.metalness[0].samples else: # Cycles scene.cycles.samples = \ - scene.cycles.preview_samples = scene.grabDoc.samplesMetalness + scene.cycles.preview_samples = scene.gd.metalness[0].samples - render.engine = str(scene.grabDoc.engineMetalness).upper() + render.engine = str(scene.gd.metalness[0].engine).upper() set_color_management('sRGB') - add_ng_to_mat(self, context, setup_type=Global.NG_METALNESS_NAME) + add_ng_to_mat(self, name=Global.METALNESS_NG_NAME, objects=objects) # ##### BEGIN GPL LICENSE BLOCK ##### diff --git a/utils/generic.py b/utils/generic.py index e43ba9d..f7eba41 100644 --- a/utils/generic.py +++ b/utils/generic.py @@ -8,7 +8,6 @@ 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: @@ -31,9 +30,9 @@ def poll(cls, context: Context) -> bool: ) -def export_bg_plane(context: Context) -> None: +def export_plane(context: Context) -> None: """Export the grabdoc background plane for external use""" - gd = context.scene.grabDoc + gd = context.scene.gd # Save original selection savedSelection = context.selected_objects @@ -48,8 +47,8 @@ def export_bg_plane(context: Context) -> None: bpy.ops.export_scene.fbx( filepath=os.path.join( - bpy.path.abspath(gd.exportPath), - gd.exportName + '_plane.fbx' + bpy.path.abspath(gd.export_path), + gd.export_name + '_plane.fbx' ), use_selection=True ) @@ -60,7 +59,7 @@ def export_bg_plane(context: Context) -> None: for ob in savedSelection: ob.select_set(True) - if not gd.collSelectable: + if not gd.coll_selectable: bpy.data.collections[Global.COLL_NAME].hide_select = False @@ -78,7 +77,9 @@ def proper_scene_setup() -> bool: 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'] + 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 @@ -135,18 +136,18 @@ def poll_message_error( 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}." + 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 get_format() -> str: + """Get the correct file extension based on `format` attribute""" + return f".{Global.IMAGE_FORMATS[bpy.context.scene.gd.format]}" def bad_setup_check( - self, context: Context, active_export: bool, report_value=False, @@ -155,11 +156,7 @@ def bad_setup_check( """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) + gd = context.scene.gd # Look for Trim Camera (only thing required to render) if not Global.TRIM_CAMERA_NAME in context.view_layer.objects \ @@ -168,41 +165,41 @@ def bad_setup_check( report_string = Error.TRIM_CAM_NOT_FOUND # Check for no objects in manual collection - if gd.useBakeCollection and not report_value: + if gd.use_bake_collections 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)) \ + if not os.path.exists(bpy.path.abspath(gd.export_path)) \ 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 + gd.normals[0].enabled, + gd.curvature[0].enabled, + gd.occlusion[0].enabled, + gd.height[0].enabled, + gd.id[0].enabled, + gd.alpha[0].enabled, + gd.color[0].enabled, + gd.roughness[0].enabled, + gd.metalness[0].enabled ) bake_map_vis = ( - gd.uiVisibilityNormals, - gd.uiVisibilityCurvature, - gd.uiVisibilityOcclusion, - gd.uiVisibilityHeight, - gd.uiVisibilityMatID, - gd.uiVisibilityAlpha, - gd.uiVisibilityAlbedo, - gd.uiVisibilityRoughness, - gd.uiVisibilityMetalness + gd.normals[0].ui_visibility, + gd.curvature[0].ui_visibility, + gd.occlusion[0].ui_visibility, + gd.height[0].ui_visibility, + gd.id[0].ui_visibility, + gd.alpha[0].ui_visibility, + gd.color[0].ui_visibility, + gd.roughness[0].ui_visibility, + gd.metalness[0].ui_visibility ) if True not in bake_maps or True not in bake_map_vis: diff --git a/utils/node.py b/utils/node.py index 8e3f33f..9e763cd 100644 --- a/utils/node.py +++ b/utils/node.py @@ -1,9 +1,11 @@ +from typing import Iterable + import bpy from bpy.types import ( Object, ShaderNodeGroup, NodeSocket, - Context, + NodeTree, Material ) @@ -11,58 +13,80 @@ 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 +def generate_ng_interface(tree: NodeTree, inputs: dict) -> None: + tree.interface.new_socket( + name="Shader", + socket_type="NodeSocketShader", + in_out='OUTPUT' + ) + saved_links = tree.interface.new_panel( + name="Saved Links", + description="Stored links to restore original socket links later", + default_closed=True + ) + for name, socket_type in inputs.items(): + tree.interface.new_socket( + name=name, + parent=saved_links, + socket_type=socket_type, + in_out='INPUT' + ) - # Create a dummy material output node to harvest potential inputs - ng_dummy_output = bpy.data.node_groups.new( + +def get_shader_outputs() -> dict: + material_output_inputs = {} + tree = bpy.data.node_groups.new( 'Material Output', 'ShaderNodeTree' ) - output = ng_dummy_output.nodes.new( + output = tree.nodes.new( 'ShaderNodeOutputMaterial' ) - - collected_inputs = {} + material_output_inputs = {} for node_input in output.inputs: - # NOTE: I have no idea why this secret input exists + # NOTE: No clue what this input is for if node_input.name == 'Thickness': continue - collected_inputs[node_input.name] = \ + material_output_inputs[node_input.name] = \ f'NodeSocket{node_input.type.capitalize()}' + return material_output_inputs - # 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') +def ng_setup() -> None: + """Initial setup of all node groups when setting up a file""" + gd = bpy.context.scene.gd - tree.interface.new_panel(name="My Panel") + # Get material output inputs + inputs = get_shader_outputs() - 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' + # Normals + if not Global.NORMAL_NG_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new( + Global.NORMAL_NG_NAME, 'ShaderNodeTree' ) - for key, value in collected_inputs.items(): - tree.interface.new_socket(name=f'Saved {key}', socket_type=value) - alpha_input = tree.interface.new_socket( + tree.use_fake_user = True + + # Create interface + generate_ng_interface(tree, inputs) + alpha = tree.interface.new_socket( name='Alpha', socket_type='NodeSocketFloat' ) - alpha_input.default_value = 1 - tree.interface.new_socket(name='Normal', socket_type='NodeSocketVector') + alpha.default_value = 1 + tree.interface.new_socket( + name='Normal', + socket_type='NodeSocketVector', + in_out='INPUT' + ) + + # Create nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" + group_input = tree.nodes.new('NodeGroupInput') + group_input.name = "Group Input" + group_input.location = (-1400,100) - # Create group nodes bevel = tree.nodes.new('ShaderNodeBevel') bevel.name = "Bevel" bevel.inputs[0].default_value = 0 @@ -83,7 +107,8 @@ def ng_setup() -> None: 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[1] = \ + -.5 if gd.normals[0].flip_y else .5 vec_mult.inputs[1].default_value[2] = -.5 vec_mult.location = (-600,0) @@ -91,8 +116,7 @@ def ng_setup() -> None: 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.inputs[1].default_value[2] = 0.5 vec_add.location = (-400,0) invert = tree.nodes.new('ShaderNodeInvert') @@ -115,239 +139,245 @@ def ng_setup() -> None: 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') + links = tree.links + # NOTE: Branch 1 + links.new(bevel.inputs["Normal"], group_input.outputs["Normal"]) + links.new(vec_transform.inputs["Vector"], bevel_node_2.outputs["Normal"]) + links.new(vec_mult.inputs["Vector"], vec_transform.outputs["Vector"]) + links.new(vec_add.inputs["Vector"], vec_mult.outputs["Vector"]) + links.new(group_output.inputs["Shader"], vec_add.outputs["Vector"]) + # NOTE: Branch 2 + links.new(invert.inputs['Color'], group_input.outputs['Alpha']) + links.new(subtract.inputs['Color2'], invert.outputs['Color']) + links.new(mix_shader.inputs['Fac'], subtract.outputs['Color']) + links.new(mix_shader.inputs[1], transp_shader.outputs['BSDF']) + links.new(mix_shader.inputs[2], vec_add.outputs['Vector']) + + # Ambient Occlusion + if not Global.AO_NG_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new(Global.AO_NG_NAME, 'ShaderNodeTree') + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) + + # Create nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" + + ao = tree.nodes.new('ShaderNodeAmbientOcclusion') ao.name = "Ambient Occlusion" ao.samples = 32 ao.location = (-600,0) - gamma = ng_ao.nodes.new('ShaderNodeGamma') + gamma = tree.nodes.new('ShaderNodeGamma') gamma.name = "Gamma" - gamma.inputs[1].default_value = gd.gammaOcclusion + gamma.inputs[1].default_value = gd.occlusion[0].gamma gamma.location = (-400,0) - emission = ng_ao.nodes.new('ShaderNodeEmission') + emission = tree.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, + links = tree.links + links.new(gamma.inputs["Color"], ao.outputs["Color"]) + links.new(emission.inputs["Color"], gamma.outputs["Color"]) + links.new(group_output.inputs["Shader"], emission.outputs["Emission"]) + + # Height + if not Global.HEIGHT_NG_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new( + Global.HEIGHT_NG_NAME, 'ShaderNodeTree' ) - ng_height.use_fake_user = True + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) - # 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 nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" - # Create group nodes - camera = ng_height.nodes.new('ShaderNodeCameraData') + camera = tree.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 = tree.nodes.new('ShaderNodeMapRange') map_range.name = "Map Range" map_range.location = (-600,0) - ramp = ng_height.nodes.new('ShaderNodeValToRGB') + ramp = tree.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, + links = tree.links + links.new(map_range.inputs["Value"], camera.outputs["View Z Depth"]) + links.new(ramp.inputs["Fac"], map_range.outputs["Result"]) + links.new(group_output.inputs["Shader"], ramp.outputs["Color"]) + + # Alpha + if not Global.ALPHA_NG_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new( + Global.ALPHA_NG_NAME, 'ShaderNodeTree' ) - ng_alpha.use_fake_user = True + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) - # 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 nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" - # Create group nodes - camera = ng_alpha.nodes.new('ShaderNodeCameraData') + camera = tree.nodes.new('ShaderNodeCameraData') camera.name = "Camera Data" camera.location = (-800,0) - gd_camera_ob_z = \ + camera_object_z = \ bpy.data.objects.get(Global.TRIM_CAMERA_NAME).location[2] - map_range = ng_alpha.nodes.new('ShaderNodeMapRange') + map_range = tree.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 + map_range.inputs[1].default_value = camera_object_z - .00001 + map_range.inputs[2].default_value = camera_object_z - invert = ng_alpha.nodes.new('ShaderNodeInvert') + invert = tree.nodes.new('ShaderNodeInvert') invert.name = "Invert" invert.location = (-400,0) - emission = ng_alpha.nodes.new('ShaderNodeEmission') + emission = tree.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 = \ + links = tree.links + links.new(map_range.inputs["Value"], camera.outputs["View Z Depth"]) + links.new(invert.inputs["Color"], map_range.outputs["Result"]) + links.new(emission.inputs["Color"], invert.outputs["Color"]) + links.new(group_output.inputs["Shader"], emission.outputs["Emission"]) + + # Base Color + if not Global.COLOR_NG_NAME in bpy.data.node_groups: + tree = \ bpy.data.node_groups.new( - Global.NG_ALBEDO_NAME, + Global.COLOR_NG_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') + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) + tree.interface.new_socket( + name=Global.COLOR_NAME, + socket_type='NodeSocketColor' + ) + + # Create nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" + group_input = tree.nodes.new('NodeGroupInput') + group_input.name = "Group Input" + group_input.location = (-400,0) + + emission = tree.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, + links = tree.links + links.new(emission.inputs["Color"], group_input.outputs["Base Color"]) + links.new(group_output.inputs["Shader"], emission.outputs["Emission"]) + + # Roughness + if not Global.ROUGHNESS_NG_NAME in bpy.data.node_groups: + tree = bpy.data.node_groups.new( + Global.ROUGHNESS_NG_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') + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) + tree.interface.new_socket( + name='Roughness', + socket_type='NodeSocketFloat', + in_out='INPUT' + ) + + # Create nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" + group_input = tree.nodes.new('NodeGroupInput') + group_input.name = "Group Input" + group_input.location = (-600,0) + + invert = tree.nodes.new('ShaderNodeInvert') invert.location = (-400,0) invert.inputs[0].default_value = 0 - emission = ng_roughness.nodes.new('ShaderNodeEmission') + emission = tree.nodes.new('ShaderNodeEmission') emission.name = "Emission" emission.location = (-200,0) # Link nodes - link = ng_roughness.links - link.new( + links = tree.links + links.new( invert.inputs["Color"], - group_inputs.outputs["Roughness Input"] + group_input.outputs["Roughness"] ) - link.new( + links.new( emission.inputs["Color"], invert.outputs["Color"] ) - link.new( - group_outputs.inputs["Output"], + links.new( + group_output.inputs["Shader"], emission.outputs["Emission"] ) - # METALNESS - if not Global.NG_METALNESS_NAME in bpy.data.node_groups: - ng_roughness = \ + # Metalness + if not Global.METALNESS_NG_NAME in bpy.data.node_groups: + tree = \ bpy.data.node_groups.new( - Global.NG_METALNESS_NAME, + Global.METALNESS_NG_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') + tree.use_fake_user = True + + # Create sockets + generate_ng_interface(tree, inputs) + tree.interface.new_socket( + name='Metalness', + socket_type='NodeSocketFloat', + in_out='INPUT' + ) + + # Create nodes + group_output = tree.nodes.new('NodeGroupOutput') + group_output.name = "Group Output" + group_input = tree.nodes.new('NodeGroupInput') + group_input.name = "Group Input" + group_input.location = (-400,0) + + emission = tree.nodes.new('ShaderNodeEmission') emission.name = "Emission" emission.location = (-200,0) # Link nodes - link = ng_roughness.links - link.new( + links = tree.links + links.new( emission.inputs["Color"], - group_inputs.outputs["Metalness Input"] + group_input.outputs["Metalness"] ) - link.new( - group_outputs.inputs["Output"], + links.new( + group_output.inputs["Shader"], emission.outputs["Emission"] ) @@ -381,19 +411,17 @@ def create_apply_ng_mat(ob: Object) -> None: ob.active_material = mat -def bsdf_link_factory( - input_name: list, +def create_bsdf_link( + input_name: str, node_group: ShaderNodeGroup, original_input: NodeSocket, - mat_slot: Material + material: Material ) -> bool: - """Add node group to all materials, save original - links, and link the node group to material output""" + """Add node group to given material slots and save original links""" node_found = False for link in original_input.links: node_found = True - - mat_slot.node_tree.links.new( + material.node_tree.links.new( node_group.inputs[input_name], link.from_node.outputs[link.from_socket.name] ) @@ -412,15 +440,9 @@ def bsdf_link_factory( 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 - +def add_ng_to_mat(self, name: str, objects: Iterable[Object]) -> None: + """Add node group to given object material slots""" + for ob in objects: # 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: @@ -428,14 +450,14 @@ def add_ng_to_mat(self, context: Context, setup_type: str) -> None: # Cycle through all material slots for slot in ob.material_slots: - mat_slot = bpy.data.materials.get(slot.name) - mat_slot.use_nodes = True + material = bpy.data.materials.get(slot.name) + material.use_nodes = True - nodes = mat_slot.node_tree.nodes - if setup_type in nodes: + nodes = material.node_tree.nodes + if name in nodes: continue - # Get materials Output Material node(s) + # Get output material node(s) output_nodes = { mat for mat in nodes if mat.type == 'OUTPUT_MATERIAL' } @@ -444,17 +466,17 @@ def add_ng_to_mat(self, context: Context, setup_type: str) -> None: nodes.new('ShaderNodeOutputMaterial') ) - node_group = bpy.data.node_groups.get(setup_type) + node_group = bpy.data.node_groups.get(name) 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 = ( + passthrough_ng = nodes.new('ShaderNodeGroup') + passthrough_ng.node_tree = node_group + passthrough_ng.location = ( output.location[0], output.location[1] - 160 ) - GD_node_group.name = node_group.name - GD_node_group.hide = True + passthrough_ng.name = node_group.name + passthrough_ng.hide = True # Add note next to node group explaining basic functionality GD_text = bpy.data.texts.get('_grabdoc_ng_warning') @@ -474,96 +496,102 @@ def add_ng_to_mat(self, context: Context, setup_type: str) -> None: 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 nodes + for output_material_input in output.inputs: + for link in output_material_input.links: + source_node = 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] - ) + #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( + # passthrough_ng.inputs[connection_name], + # source_node.outputs[link.from_socket.name] + # ) + + # Store original output material connections + try: + material.node_tree.links.new( + passthrough_ng.inputs[ + output_material_input.name + ], + source_node.outputs[link.from_socket.name] + ) + except KeyError: + pass - # 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': + # Link dependencies from any BSDF node + if name not in Global.SHADER_MAP_NAMES \ + or "BSDF" not in source_node.type: continue - node_found = False - for original_input in original.inputs: + for original_input in source_node.inputs: + if original_input.name not in Global.ALL_MAP_NAMES: + continue if ( - setup_type == Global.NG_ALBEDO_NAME \ - and original_input.name == 'Base Color' + name == Global.COLOR_NG_NAME \ + and original_input.name == Global.COLOR_NAME ): - node_found = bsdf_link_factory( - input_name='Color Input', - node_group=GD_node_group, + node_found = create_bsdf_link( + input_name=original_input.name, + node_group=passthrough_ng, original_input=original_input, - mat_slot=mat_slot + material=material ) elif ( - setup_type == Global.NG_ROUGHNESS_NAME \ - and original_input.name == 'Roughness' + name == Global.ROUGHNESS_NG_NAME \ + and original_input.name == Global.ROUGHNESS_NAME ): - node_found = bsdf_link_factory( - input_name='Roughness Input', - node_group=GD_node_group, + node_found = create_bsdf_link( + input_name=original_input.name, + node_group=passthrough_ng, original_input=original_input, - mat_slot=mat_slot + material=material ) elif ( - setup_type == Global.NG_METALNESS_NAME \ - and original_input.name == 'Metallic' + name == Global.METALNESS_NG_NAME \ + and original_input.name == Global.METALNESS_NAME ): - node_found = bsdf_link_factory( - input_name='Metalness Input', - node_group=GD_node_group, + node_found = create_bsdf_link( + input_name=original_input.name, + node_group=passthrough_ng, original_input=original_input, - mat_slot=mat_slot + material=material ) - elif ( - setup_type == Global.NG_NORMAL_NAME \ - and original_input.name in ('Normal', 'Alpha') + if ( + name in (Global.ALPHA_NG_NAME, + Global.NORMAL_NG_NAME) \ + and original_input.name in (Global.ALPHA_NAME, + Global.NORMAL_NAME) ): - node_found = bsdf_link_factory( + node_found = create_bsdf_link( input_name=original_input.name, - node_group=GD_node_group, + node_group=passthrough_ng, original_input=original_input, - mat_slot=mat_slot + material=material ) - # Does not work if Map Preview + # TODO: 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 bpy.context.scene.gd.normals[0].use_texture \ + and material.blend_method == 'OPAQUE' \ and len(original_input.links) ): - mat_slot.blend_method = 'CLIP' + material.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 + and name != Global.NORMAL_NG_NAME \ + and material.name != Global.GD_MATERIAL_NAME ): self.report( {'WARNING'}, @@ -571,14 +599,14 @@ def add_ng_to_mat(self, context: Context, setup_type: str) -> None: ) for link in output.inputs['Volume'].links: - mat_slot.node_tree.links.remove(link) + material.node_tree.links.remove(link) for link in output.inputs['Displacement'].links: - mat_slot.node_tree.links.remove(link) + material.node_tree.links.remove(link) # Link Node Group to the output - mat_slot.node_tree.links.new( + material.node_tree.links.new( output.inputs["Surface"], - GD_node_group.outputs["Output"] + passthrough_ng.outputs["Shader"] ) @@ -625,7 +653,7 @@ def cleanup_ng_from_mat(setup_type: str) -> None: ('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}': + if node_input.name == connection_name: mat.node_tree.links.new( output.inputs[connection_name], original_node_connection.outputs[ diff --git a/utils/render.py b/utils/render.py index f6d593b..3c35bfc 100644 --- a/utils/render.py +++ b/utils/render.py @@ -1,32 +1,33 @@ -from ast import List from mathutils import Vector import bpy -from bpy.types import Context, Object +from bpy.types import Object from ..constants import GlobalVariableConstants as Global -def is_valid_grabdoc_object( +def is_valid_gd_object( ob: Object, has_prefix: bool=False, render_visible: bool=True, invalid_type: bool=True, - is_gd_ob: bool=True + is_gd_ob: bool=False ) -> 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 + if is_gd_ob and not ob.gd_object: + return False + if not is_gd_ob and ob.gd_object: + 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: + if render_visible and ob.hide_render: return False - if is_gd_ob and not ob.is_gd_object: + if invalid_type and ob.type in Global.INVALID_BAKE_TYPES: return False return True @@ -46,9 +47,9 @@ def in_viewing_frustrum(vector: Vector) -> bool: ), Vector( ( - bg_plane.dimensions.x * -1.25 + bg_plane.location[0], - bg_plane.dimensions.y * -1.25 + bg_plane.location[1], - -100 + bg_plane.dimensions.x * 1.25 + bg_plane.location[0], + bg_plane.dimensions.y * 1.25 + bg_plane.location[1], + 100 ) ) ) @@ -63,50 +64,56 @@ def in_viewing_frustrum(vector: Vector) -> bool: return True -def get_rendered_objects(context: Context) -> set | None: +def get_rendered_objects() -> 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: + objects = set() + if bpy.context.scene.gd.use_bake_collections: for coll in bpy.data.collections: - if coll.is_gd_collection is False: + if coll.gd_bake_collection is False: continue - rendered_obs.update( - [ob for ob in coll.all_objects if is_valid_grabdoc_object(ob)] + objects.update( + [ob for ob in coll.all_objects if is_valid_gd_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 + return objects - 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 + for ob in bpy.context.view_layer.objects: + if not is_valid_gd_object(ob): + continue - if in_viewing_frustrum(global_bbox_center): - rendered_obs.add(ob.name) - return rendered_obs + local_bbox_center = .125 * sum( + (Vector(ob) for ob in ob.bound_box), Vector() + ) + global_bbox_center = ob.matrix_world @ local_bbox_center + if in_viewing_frustrum(global_bbox_center): + objects.add(ob) + return objects -def set_guide_height(objects: List[Object]) -> None: +def set_guide_height(objects: list[Object]=None) -> 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 = \ + bpy.context.scene.gd.height[0].distance = \ tallest_vert - bg_plane.location[2] -def find_tallest_object(objects: List[Object]) -> None: +def find_tallest_object(objects: list[Object]=None) -> float: """Find the tallest points in the viewlayer by looping through objects to find the highest vertex on the Z axis""" + if objects is None: + objects = bpy.context.selectable_objects + depsgraph = bpy.context.evaluated_depsgraph_get() tallest_verts = [] - objects = [ob for ob in objects if ob.name.startswith(Global.GD_PREFIX)] + objects = [ + ob for ob in objects if not ob.name.startswith(Global.GD_PREFIX) + ] for ob in objects: ob_eval = ob.evaluated_get(depsgraph) mesh_eval = ob_eval.to_mesh() @@ -122,7 +129,8 @@ def find_tallest_object(objects: List[Object]) -> None: tallest_verts.append(max_z_co) ob_eval.to_mesh_clear() if not tallest_verts: - return None + # NOTE: Fallback to manual height value + return bpy.context.scene.gd.height[0].distance return max(tallest_verts) diff --git a/utils/scene.py b/utils/scene.py index a57d78c..96b1a1a 100644 --- a/utils/scene.py +++ b/utils/scene.py @@ -22,7 +22,7 @@ def remove_setup(context: Context, hard_reset: bool=True) -> None | list: 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.useBakeCollection: + if hard_reset or not context.scene.gd.use_bake_collections: context.scene.collection.objects.link(ob) else: saved_bake_group_obs.append(ob) @@ -68,9 +68,9 @@ def remove_setup(context: Context, hard_reset: bool=True) -> None | list: 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(Global.GD_PREFIX): - bpy.data.node_groups.remove(ngroup) + for group in bpy.data.node_groups: + if group.name.startswith(Global.GD_PREFIX): + bpy.data.node_groups.remove(group) return None @@ -92,9 +92,9 @@ def remove_setup(context: Context, hard_reset: bool=True) -> None | list: # Camera # Forcibly exit the camera before deleting it so # the original users camera position is retained - reposition_cam = False + update_camera = False if is_camera_in_3d_view(): - reposition_cam = True + update_camera = True bpy.ops.view3d.view_camera() if Global.TRIM_CAMERA_NAME in bpy.data.cameras: @@ -104,13 +104,13 @@ def remove_setup(context: Context, hard_reset: bool=True) -> None | list: 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 + return saved_plane_loc, saved_plane_rot, saved_mat, update_camera, saved_bake_group_obs -# NOTE:: 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""" - gd = context.scene.grabDoc + gd = context.scene.gd view_layer = context.view_layer # PRELIMINARY @@ -131,30 +131,46 @@ def scene_setup(_self, context: Context) -> None: gd_coll.hide_viewport = False if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') # Deselect all objects for ob in context.selected_objects: 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 all related GrabDoc data blocks but store necessary values + saved_plane_loc, saved_plane_rot, \ + saved_mat, update_camera, 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 = gd.exportResX - context.scene.render.resolution_y = gd.exportResY + # Set scene resolution + context.scene.render.resolution_x = gd.export_res_x + context.scene.render.resolution_y = gd.export_res_y + + # PROPERTIES + for map_name in gd.MAP_TYPES: + try: + prop = getattr(gd, map_name[0]) + if prop: + continue + item = prop.add() + item.suffix = map_name[0] + item.engine.items = ( + ('blender_eevee', "Eevee", ""), + ('cycles', "Cycles", "") + ) + except AttributeError: + continue # COLLECTIONS # Create a bake group collection if requested - if gd.useBakeCollection: - bake_group_coll = bpy.data.collections.new(name=Global.COLL_OB_NAME) - bake_group_coll.is_gd_collection = True + if gd.use_bake_collections: + bake_group_coll = bpy.data.collections.new(Global.COLL_OB_NAME) + bake_group_coll.gd_bake_collection = True context.scene.collection.children.link(bake_group_coll) - view_layer.active_layer_collection = view_layer.layer_collection.children[-1] + view_layer.active_layer_collection = \ + view_layer.layer_collection.children[-1] if len(saved_bake_group_obs): for ob in saved_bake_group_obs: @@ -162,10 +178,11 @@ def scene_setup(_self, context: Context) -> None: # Create main GrabDoc collection gd_coll = bpy.data.collections.new(name=Global.COLL_NAME) - gd_coll.is_gd_collection = True + gd_coll.gd_bake_collection = True context.scene.collection.children.link(gd_coll) - view_layer.active_layer_collection = view_layer.layer_collection.children[-1] + view_layer.active_layer_collection = \ + view_layer.layer_collection.children[-1] # Make the GrabDoc collection the active collection view_layer.active_layer_collection = \ @@ -174,7 +191,7 @@ def scene_setup(_self, context: Context) -> None: # BG PLANE bpy.ops.mesh.primitive_plane_add( - size=gd.scalingSet, + size=gd.scale, calc_uvs=True, align='WORLD', location=saved_plane_loc, @@ -186,29 +203,30 @@ def scene_setup(_self, context: Context) -> None: plane_ob = bpy.data.objects[Global.BG_PLANE_NAME] # Prepare proper plane scaling - if gd.exportResX != gd.exportResY: - if gd.exportResX > gd.exportResY: - div_factor = gd.exportResX / gd.exportResY - + if gd.export_res_x != gd.export_res_y: + if gd.export_res_x > gd.export_res_y: + div_factor = gd.export_res_x / gd.export_res_y plane_ob.scale[1] /= div_factor else: - div_factor = gd.exportResY / gd.exportResX - + div_factor = gd.export_res_y / gd.export_res_x plane_ob.scale[0] /= div_factor - bpy.ops.object.transform_apply(location=False, rotation=False) plane_ob.select_set(False) - plane_ob.is_gd_object = True + # NOTE: Skip enabling gd_object + # as we always want it to render + #plane_ob.gd_object = True plane_ob.show_wire = True - plane_ob.lock_scale[0] = plane_ob.lock_scale[1] = plane_ob.lock_scale[2] = 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 gd.refSelection and not gd.modalState: + if gd.reference and not gd.preview_state: for mat in bpy.data.materials: if mat.name == Global.REFERENCE_NAME: - mat.node_tree.nodes.get('Image Texture').image = gd.refSelection + mat.node_tree.nodes.get('Image Texture').image = gd.reference break else: # Create a new material & turn on node use @@ -222,7 +240,7 @@ def scene_setup(_self, context: Context) -> None: mat.node_tree.nodes.remove(mat.node_tree.nodes.get('Principled BSDF')) image = mat.node_tree.nodes.new('ShaderNodeTexImage') - image.image = gd.refSelection + image.image = gd.reference image.location = (-300,0) # Link materials @@ -248,7 +266,7 @@ def scene_setup(_self, context: Context) -> None: bpy.data.materials.remove(bpy.data.materials[Global.REFERENCE_NAME]) # Grid for better snapping and measurements - if gd.gridSubdivisions and gd.useGrid: + if gd.grid_subdivs and gd.use_grid: # Create & load new bmesh bm = bmesh.new() bm.from_mesh(plane_ob.data) @@ -257,7 +275,7 @@ def scene_setup(_self, context: Context) -> None: bmesh.ops.subdivide_edges( bm, edges=bm.edges, - cuts=gd.gridSubdivisions, + cuts=gd.grid_subdivs, use_grid_fill=True ) @@ -270,31 +288,30 @@ def scene_setup(_self, context: Context) -> None: 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 * gd.scalingSet) + trim_cam_ob.location = (0, 0, 15 * gd.scale) trim_cam_ob.parent = plane_ob - trim_cam_ob.is_gd_object = True + trim_cam_ob.gd_object = True # 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 = gd.scalingSet + trim_cam_data.ortho_scale = gd.scale + # NOTE: Match cameras clipping distance to scale trim_cam_data.clip_start = 0.1 - # NOTE: Match scale to cameras clipping distance - trim_cam_data.clip_end = 1000 * (gd.scalingSet / 25) + trim_cam_data.clip_end = 1000 * (gd.scale / 25) gd_coll.objects.link(trim_cam_ob) context.scene.camera = trim_cam_ob - if reposition_cam: + if update_camera: # 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 gd.exportHeight and gd.rangeTypeHeight == 'MANUAL': + if gd.height[0].enabled and gd.height[0].method == 'MANUAL': generate_manual_height_guide_mesh(Global.HEIGHT_GUIDE_NAME, plane_ob) # ORIENT GUIDE @@ -325,9 +342,9 @@ def scene_setup(_self, context: Context) -> None: pass # Hide collections & make unselectable if requested (run this after everything else) - gd_coll.hide_select = not gd.collSelectable - gd_coll.hide_viewport = not gd.collVisible - gd_coll.hide_render = not gd.collRendered + gd_coll.hide_select = not gd.coll_selectable + gd_coll.hide_viewport = not gd.coll_visible + gd_coll.hide_render = not gd.coll_rendered def generate_manual_height_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) -> None: @@ -339,7 +356,7 @@ def generate_manual_height_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) 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 + (vec[0], vec[1], bpy.context.scene.gd.height[0].distance) for vec in camera_corner_vecs ] ring_vecs = [ (vec[0], vec[1], vec[2] + 1) for vec in camera_corner_vecs @@ -365,7 +382,7 @@ def generate_manual_height_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) bpy.context.collection.objects.link(new_ob) # Parent the height guide to the BG Plane - new_ob.is_gd_object = True + new_ob.gd_object = True new_ob.parent = plane_ob new_ob.hide_select = True @@ -394,7 +411,7 @@ def generate_plane_orient_guide_mesh(ob_name: str, plane_ob: bpy.types.Object) - # Parent the height guide to the BG Plane new_ob.parent = plane_ob new_ob.hide_select = True - new_ob.is_gd_object = True + new_ob.gd_object = True # ##### BEGIN GPL LICENSE BLOCK #####