From 5d5da3affcbd1241e28c5c201166901b5e7b3c61 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:22:09 -0400 Subject: [PATCH 01/26] check os before trying dep install --- i_scene_cp77_gltf/install_dependency.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i_scene_cp77_gltf/install_dependency.py b/i_scene_cp77_gltf/install_dependency.py index c2ec715..57bd4e9 100644 --- a/i_scene_cp77_gltf/install_dependency.py +++ b/i_scene_cp77_gltf/install_dependency.py @@ -1,6 +1,14 @@ import bpy +import sys +from .main.common import show_message + +_os = sys.platform def install_dependency(dependency_name): + if _os is not 'win32': + print(f"required package: {dependency_name} not found but the plugin is unable to install this automatically on OS other than Windows") + show_message(f"required package: {dependency_name} not found but the plugin is unable to install automatically on OS other than Windows") + return('CANCELLED') print(f"required package: {dependency_name} not found") from pip import _internal as pip print(f"Attempting to install {dependency_name}") From d893d526ff7bb6a0479fe3c10ae24c2e150fc432 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:25:37 -0400 Subject: [PATCH 02/26] add bool for attempting pyyaml install --- i_scene_cp77_gltf/cyber_props.py | 6 ++ i_scene_cp77_gltf/exporters/__init__.py | 8 ++- i_scene_cp77_gltf/exporters/sectors_export.py | 67 ++++++++++--------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/i_scene_cp77_gltf/cyber_props.py b/i_scene_cp77_gltf/cyber_props.py index 351eb54..7eeed97 100644 --- a/i_scene_cp77_gltf/cyber_props.py +++ b/i_scene_cp77_gltf/cyber_props.py @@ -248,6 +248,12 @@ class CP77_PT_PanelProps(PropertyGroup): name="With Materials", default=True, description="Import Wolvenkit-exported materials" + ) + + axl_yaml: BoolProperty( + name="Use YAML instead of JSON", + default=False, + description="Use the ArchiveXL YAML format instead of JSON format for generated .xl files" ) def add_anim_props(animation, action): diff --git a/i_scene_cp77_gltf/exporters/__init__.py b/i_scene_cp77_gltf/exporters/__init__.py index 9a225e3..5a71e89 100644 --- a/i_scene_cp77_gltf/exporters/__init__.py +++ b/i_scene_cp77_gltf/exporters/__init__.py @@ -24,9 +24,15 @@ class CP77StreamingSectorExport(Operator,ExportHelper): bl_description = "Export changes to Sectors back to project" filename_ext = ".cpmodproj" filter_glob: StringProperty(default="*.cpmodproj", options={'HIDDEN'}) + + def draw(self, context): + props = context.scene.cp77_panel_props + layout = self.layout + layout.prop(props, "axl_yaml") def execute(self, context): - exportSectors(self.filepath) + use_yaml = context.scene.cp77_panel_props.axl_yaml + exportSectors(self.filepath, use_yaml) return {'FINISHED'} diff --git a/i_scene_cp77_gltf/exporters/sectors_export.py b/i_scene_cp77_gltf/exporters/sectors_export.py index 4d9ef3c..8277bc9 100644 --- a/i_scene_cp77_gltf/exporters/sectors_export.py +++ b/i_scene_cp77_gltf/exporters/sectors_export.py @@ -33,6 +33,7 @@ from ..main.common import * from mathutils import Vector, Matrix, Quaternion from os.path import join +from ..cyber_props import * def are_matrices_equal(mat1, mat2, tolerance=0.01): if len(mat1) != len(mat2): @@ -52,36 +53,41 @@ def are_matrices_equal(mat1, mat2, tolerance=0.01): # pip.main(['install', 'pyyaml']) # yamlavail=False -try: - import yaml - yamlavail=True -except ModuleNotFoundError: - from ..install_dependency import * - install_dependency('pyyaml') - messages = [ - "pyyaml not available. Please start Blender as administrator." - "If that doesn't help, switch to Blender's scripting perspective, create a new file, and put the following code in it (no indentation):", - "\timport pip", - "\tpip.main(['install', 'pyyaml'])", - ] - - blender_install_path = next(iter(bpy.utils.script_paths()), None) - - if blender_install_path is not None: - blender_install_path = join(blender_install_path, "..") - blender_python_path = join(blender_install_path, "python", "bin", "python.exe") - blender_module_path = join(blender_install_path, "python", "lib", "site-packages") - - messages.append("If that doesn't help either, run the following command from an administrator command prompt:") - messages.append(f"\t\"{blender_python_path}\" -m pip install pyyaml -t \"{blender_module_path}\"") - - messages.append("You can learn more about running Blender scripts under https://tinyurl.com/cp2077blenderpython") - for message in messages: - show_message(message) - print(message) - C = bpy.context +def try_import_yaml(): + try: + import yaml + yamlavail=True + except ModuleNotFoundError: + from ..install_dependency import install_dependency + try: + install_dependency('pyyaml') + except Exception as e: + print(e) + show_message('Attempted install of PYYAML failed, please see console for more information') + messages = [ + "pyyaml not available. Please start Blender as administrator." + "If that doesn't help, switch to Blender's scripting perspective, create a new file, and put the following code in it (no indentation):", + "\timport pip", + "\tpip.main(['install', 'pyyaml'])", + ] + + blender_install_path = next(iter(bpy.utils.script_paths()), None) + + if blender_install_path is not None: + blender_install_path = join(blender_install_path, "..") + blender_python_path = join(blender_install_path, "python", "bin", "python.exe") + blender_module_path = join(blender_install_path, "python", "lib", "site-packages") + + messages.append("If that doesn't help either, run the following command from an administrator command prompt:") + messages.append(f"\t\"{blender_python_path}\" -m pip install pyyaml -t \"{blender_module_path}\"") + + messages.append("You can learn more about running Blender scripts under https://tinyurl.com/cp2077blenderpython") + for message in messages: + print(message) + + # function to recursively count nested collections def countChildNodes(collection): if 'expectedNodes' in collection: @@ -381,7 +387,7 @@ def create_static_from_WIMN(node, template_nodes, newHID): newHID+=1 -def exportSectors( filename): +def exportSectors(filename, use_yaml): #Set this to your project directory #filename= '/Volumes/Ruby/archivexlconvert/archivexlconvert.cdproj' #project = '/Volumes/Ruby/archivexlconvert/' @@ -1046,7 +1052,8 @@ def exportSectors( filename): sectpathout=os.path.join(projpath,os.path.splitext(os.path.basename(filename))[0]+'.streamingsector.json') with open(sectpathout, 'w') as outfile: json.dump(template_json, outfile,indent=2) - + if use_yaml: + try_import_yaml() xlpathout=os.path.join(xloutpath,os.path.splitext(os.path.basename(filename))[0]+'.archive.xl') to_archive_xl(xlpathout, deletions, expectedNodes) print('Finished exporting sectors from ',os.path.splitext(os.path.basename(filename))[0], ' to ',sectpathout ) From a144bc8012878bb50f4bcf792ec77fef4cc765b2 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:26:03 -0400 Subject: [PATCH 03/26] update logging style for sector import --- i_scene_cp77_gltf/importers/sector_import.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/i_scene_cp77_gltf/importers/sector_import.py b/i_scene_cp77_gltf/importers/sector_import.py index 63631c3..a030fe4 100644 --- a/i_scene_cp77_gltf/importers/sector_import.py +++ b/i_scene_cp77_gltf/importers/sector_import.py @@ -245,7 +245,12 @@ def get_tan_pos(inst): return pos def importSectors( filepath='', want_collisions=False, am_modding=False, with_materials=True, remap_depot=False, with_lights=True ): - + cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences + if not cp77_addon_prefs.non_verbose: + print('') + print('-------------------- Importing Cyberpunk 2077 Streaming Sectors --------------------') + print('') + start_time = time.time() # Enter the path to your projects source\raw\base folder below, needs double slashes between folder names. path = os.path.join( os.path.dirname(filepath),'source','raw','base') print('path is ',path) @@ -1249,10 +1254,11 @@ def importSectors( filepath='', want_collisions=False, am_modding=False, with_ma nextpoint.handle_right = righthandlepos # Set the points to be the same nextpoint.co=endpoint.co - - - print('Finished Importing Sectors') - + + print(f"Imported Sector: {sectorName} in {time.time() - start_time}") + print('') + print('-------------------- Finished Importing Cyberpunk 2077 Streaming Sectors --------------------') + print('') # The above is the code thats for the import plugin below is to allow testing/dev, you can run this file to import something From 1aa6f7640130e83e4dabf290a1d939048154248c Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:26:33 -0400 Subject: [PATCH 04/26] update animtools poll functions --- i_scene_cp77_gltf/animtools/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/i_scene_cp77_gltf/animtools/__init__.py b/i_scene_cp77_gltf/animtools/__init__.py index 3ab7c28..6ed5291 100644 --- a/i_scene_cp77_gltf/animtools/__init__.py +++ b/i_scene_cp77_gltf/animtools/__init__.py @@ -33,16 +33,16 @@ class CP77_PT_AnimsPanel(Panel): def poll(cls, context): cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences if cp77_addon_prefs.context_only: - return context.active_object and context.active_object.type == 'ARMATURE' + return context.active_object and is_armature(context.active_object) is True else: return context - + ## make sure the context is unrestricted as possible, ensure there's an armature selected def draw(self, context): layout = self.layout cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences - + if cp77_addon_prefs.show_animtools: props = context.scene.cp77_panel_props box = layout.box() @@ -95,7 +95,7 @@ def draw(self, context): row = box.row(align=True) row.prop(bpy.context.scene, 'frame_start', text="") row.prop(bpy.context.scene, 'frame_end', text="") - + box = layout.box() row = box.row(align=True) row.label(text='Animsets', icon_value=get_icon('WKIT')) @@ -142,8 +142,7 @@ class CP77AnimsDelete(Operator): @classmethod def poll(cls, context): - return context.active_object and context.active_object.animation_data - + return is_armature(context.active_object) def execute(self, context): delete_anim(self, context) return{'FINISHED'} @@ -162,7 +161,7 @@ class CP77Animset(Operator): @classmethod def poll(cls, context): - return context.active_object and context.active_object.animation_data + return is_armature(context.active_object) def execute(self, context): obj = context.active_object @@ -208,7 +207,7 @@ class CP77BoneHider(Operator): bl_options = {'REGISTER', 'UNDO'} bl_label = "Toggle Deform Bone Visibilty" bl_description = "Hide deform bones in the selected armature" - + def execute(self, context): hide_extra_bones(self, context) return{'FINISHED'} @@ -220,7 +219,7 @@ class CP77BoneUnhider(Operator): bl_options = {'REGISTER', 'UNDO'} bl_label = "Toggle Deform Bone Visibilty" bl_description = "Unhide deform bones in the selected armature" - + def execute(self, context): unhide_extra_bones(self, context) return{'FINISHED'} @@ -240,7 +239,7 @@ def execute(self, context): props = context.scene.cp77_panel_props cp77_keyframe(props, context, props.frameall) return {"FINISHED"} - + def draw(self, context): layout = self.layout props = context.scene.cp77_panel_props @@ -298,7 +297,7 @@ class CP77RigLoader(Operator): appearances: StringProperty(name="Appearances", default="") directory: StringProperty(name="Directory", default="") filepath: StringProperty(name="Filepath", default="") - + def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) @@ -316,6 +315,7 @@ def execute(self, context): if props.fbx_rot: rotate_quat_180(self,context) return {'FINISHED'} + def draw(self,context): props = context.scene.cp77_panel_props layout = self.layout @@ -332,7 +332,7 @@ class CP77AnimNamer(Operator): bl_label = "Fix Action Names" bl_options = {'INTERNAL', 'UNDO'} bl_description = "replace spaces and capital letters in animation names with underscores and lower case letters" - + def execute(self, context): for a in CP77AnimsList(self,context): a.name = a.name.replace(" ", "_").lower() return {'FINISHED'} From 9330ed4f6c5c8ab7c0b3fb8b51f6c8bff44460ea Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:27:13 -0400 Subject: [PATCH 05/26] add matrix conversions to bartmoss --- i_scene_cp77_gltf/main/bartmoss_functions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index cbd2011..209f680 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -6,6 +6,12 @@ ## I get that these are lazy but they're convenient type checks def is_mesh(o: bpy.types.Object) -> bool: return isinstance(o.data, bpy.types.Mesh) + +def world_mtx(armature, bone): + return armature.convert_space(bone, bone.matrix, from_space='POSE', to_space='WORLD') + +def pose_mtx(armature, bone, mat): + return armature.convert_space(bone, mat, from_space='WORLD', to_space='POSE') def is_armature(o: bpy.types.Object) -> bool: # I just found out I could leave annotations like that -> future presto will appreciate knowing wtf I though I was going to return return isinstance(o.data, bpy.types.Armature) From e0d0789c9c29c4143b4fa2783c403e1f2f39dfab Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:27:35 -0400 Subject: [PATCH 06/26] add timing and print statements to phys import --- i_scene_cp77_gltf/importers/phys_import.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/i_scene_cp77_gltf/importers/phys_import.py b/i_scene_cp77_gltf/importers/phys_import.py index 04bc376..afac774 100644 --- a/i_scene_cp77_gltf/importers/phys_import.py +++ b/i_scene_cp77_gltf/importers/phys_import.py @@ -1,9 +1,12 @@ import json import bpy import bmesh +import time from ..collisiontools.collisions import draw_box_collider, draw_convex_collider, set_collider_props def cp77_phys_import(filepath, rig=None, chassis_z=None): + cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences + start_time = time.time() physJsonPath = filepath collision_type = 'VEHICLE' for area in bpy.context.screen.areas: @@ -88,4 +91,6 @@ def cp77_phys_import(filepath, rig=None, chassis_z=None): if chassis_z is not None: capsule.delta_location[2] = chassis_z bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - new_collection.objects.link(capsule) \ No newline at end of file + new_collection.objects.link(capsule) + if not cp77_addon_prefs.non_verbose: + print(f"phys collider Import Time: {(time.time() - start_time)} Seconds") From d034e2027e3b6faedbaa08017bd9d2e0fc4176dc Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:16:31 -0400 Subject: [PATCH 07/26] Revert "update animtools poll functions" This reverts commit 1aa6f7640130e83e4dabf290a1d939048154248c. --- i_scene_cp77_gltf/animtools/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/i_scene_cp77_gltf/animtools/__init__.py b/i_scene_cp77_gltf/animtools/__init__.py index 6ed5291..3ab7c28 100644 --- a/i_scene_cp77_gltf/animtools/__init__.py +++ b/i_scene_cp77_gltf/animtools/__init__.py @@ -33,16 +33,16 @@ class CP77_PT_AnimsPanel(Panel): def poll(cls, context): cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences if cp77_addon_prefs.context_only: - return context.active_object and is_armature(context.active_object) is True + return context.active_object and context.active_object.type == 'ARMATURE' else: return context - + ## make sure the context is unrestricted as possible, ensure there's an armature selected def draw(self, context): layout = self.layout cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences - + if cp77_addon_prefs.show_animtools: props = context.scene.cp77_panel_props box = layout.box() @@ -95,7 +95,7 @@ def draw(self, context): row = box.row(align=True) row.prop(bpy.context.scene, 'frame_start', text="") row.prop(bpy.context.scene, 'frame_end', text="") - + box = layout.box() row = box.row(align=True) row.label(text='Animsets', icon_value=get_icon('WKIT')) @@ -142,7 +142,8 @@ class CP77AnimsDelete(Operator): @classmethod def poll(cls, context): - return is_armature(context.active_object) + return context.active_object and context.active_object.animation_data + def execute(self, context): delete_anim(self, context) return{'FINISHED'} @@ -161,7 +162,7 @@ class CP77Animset(Operator): @classmethod def poll(cls, context): - return is_armature(context.active_object) + return context.active_object and context.active_object.animation_data def execute(self, context): obj = context.active_object @@ -207,7 +208,7 @@ class CP77BoneHider(Operator): bl_options = {'REGISTER', 'UNDO'} bl_label = "Toggle Deform Bone Visibilty" bl_description = "Hide deform bones in the selected armature" - + def execute(self, context): hide_extra_bones(self, context) return{'FINISHED'} @@ -219,7 +220,7 @@ class CP77BoneUnhider(Operator): bl_options = {'REGISTER', 'UNDO'} bl_label = "Toggle Deform Bone Visibilty" bl_description = "Unhide deform bones in the selected armature" - + def execute(self, context): unhide_extra_bones(self, context) return{'FINISHED'} @@ -239,7 +240,7 @@ def execute(self, context): props = context.scene.cp77_panel_props cp77_keyframe(props, context, props.frameall) return {"FINISHED"} - + def draw(self, context): layout = self.layout props = context.scene.cp77_panel_props @@ -297,7 +298,7 @@ class CP77RigLoader(Operator): appearances: StringProperty(name="Appearances", default="") directory: StringProperty(name="Directory", default="") filepath: StringProperty(name="Filepath", default="") - + def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) @@ -315,7 +316,6 @@ def execute(self, context): if props.fbx_rot: rotate_quat_180(self,context) return {'FINISHED'} - def draw(self,context): props = context.scene.cp77_panel_props layout = self.layout @@ -332,7 +332,7 @@ class CP77AnimNamer(Operator): bl_label = "Fix Action Names" bl_options = {'INTERNAL', 'UNDO'} bl_description = "replace spaces and capital letters in animation names with underscores and lower case letters" - + def execute(self, context): for a in CP77AnimsList(self,context): a.name = a.name.replace(" ", "_").lower() return {'FINISHED'} From 0a9a150c206443fe3fab32de46a092877ecc2e2e Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:42:59 -0400 Subject: [PATCH 08/26] armature retarget changes parent and collection armature retargeting now reparents the meshes and moves them to the target armature collection --- i_scene_cp77_gltf/meshtools/meshtools.py | 35 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 617f357..97e7a32 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -51,22 +51,39 @@ def CP77ArmatureSet(self, context): props = context.scene.cp77_panel_props target_armature_name = props.selected_armature target_armature = bpy.data.objects.get(target_armature_name) - if len(selected_meshes) >0: + + if len(selected_meshes) > 0: if target_armature and target_armature.type == 'ARMATURE': + # Ensure the target armature has a collection + if not target_armature.users_collection: + target_collection = bpy.data.collections.new(target_armature.name + "_collection") + bpy.context.scene.collection.children.link(target_collection) + target_collection.objects.link(target_armature) + else: + target_collection = target_armature.users_collection[0] + for mesh in selected_meshes: - retargeted=False + retargeted = False for modifier in mesh.modifiers: if modifier.type == 'ARMATURE' and modifier.object is not target_armature: modifier.object = target_armature - retargeted=True - else: - if modifier.type == 'ARMATURE' and modifier.object is target_armature: - retargeted=True - continue + retargeted = True + elif modifier.type == 'ARMATURE' and modifier.object is target_armature: + retargeted = True + continue if not retargeted: armature = mesh.modifiers.new('Armature', 'ARMATURE') - armature.object = target_armature + armature.object = target_armature + + # Set parent + mesh.parent = target_armature + + # Unlink the mesh from its original collections + for col in mesh.users_collection: + col.objects.unlink(mesh) + # Link the mesh to the target armature's collection + target_collection.objects.link(mesh) def CP77UvChecker(self, context): selected_meshes = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] @@ -257,4 +274,4 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): lattice_modifier = mesh.modifiers.new(new_lattice.name,'LATTICE') for mesh in selected_meshes: print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) - lattice_modifier.object = new_lattice + lattice_modifier.object = new_lattice \ No newline at end of file From c2422b40c6a843f33b547f5845eff1ee625a5e38 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:44:00 -0400 Subject: [PATCH 09/26] refactor glb export to separate anims and mesh export functions --- i_scene_cp77_gltf/exporters/glb_export.py | 267 ++++++++++------------ 1 file changed, 122 insertions(+), 145 deletions(-) diff --git a/i_scene_cp77_gltf/exporters/glb_export.py b/i_scene_cp77_gltf/exporters/glb_export.py index 9f6d116..c814278 100644 --- a/i_scene_cp77_gltf/exporters/glb_export.py +++ b/i_scene_cp77_gltf/exporters/glb_export.py @@ -1,5 +1,6 @@ import bpy from .. animtools import reset_armature +from ..main.common import show_message #setup the default options to be applied to all export types def default_cp77_options(): @@ -54,8 +55,6 @@ def pose_export_options(): } return options -#setup the actual exporter - rewrote almost all of this, much quicker now - red_color = (1, 0, 0, 1) # RGBA garment_cap_name="_GarmentSupportCap" garment_weight_name="_GarmentSupportWeight" @@ -93,94 +92,94 @@ def add_garment_cap(mesh): if cap_layer != None and loop_index < (len(cap_layer.data)): cap_layer.data[loop_index].color = red_color - -# setup the actual exporter - rewrote almost all of this, much quicker now # mana: by assigning default attributes, we make this update-safe (some older scripts broke). Just don't re-name them! def export_cyberpunk_glb(context, filepath, export_poses=False, export_visible=False, limit_selected=True, static_prop=False, red_garment_col=False, apply_transform=True): - groupless_bones = set() - bone_names = [] - #check if the scene is in object mode, if it's not, switch to object mode if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') objects = context.selected_objects - armatures = [obj for obj in objects if obj.type == 'ARMATURE'] - + options = default_cp77_options() + #if for photomode, make sure there's an armature selected, if not use the message box to show an error if export_poses: + armatures = [obj for obj in objects if obj.type == 'ARMATURE'] if not armatures: - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message="No armature objects are selected, please select an armature") + show_message("No armature objects are selected, please select an armature") return {'CANCELLED'} - for action in bpy.data.actions: - if "schema" not in action: - action["schema"] ={"type": "wkit.cp2077.gltf.anims","version": 4} - if "animationType" not in action: - action["animationType"] = 'Normal' - if "rootMotionType" not in action: - action["rootMotionType"] = 'None' - if "frameClamping" not in action: - action["frameClamping"] = True - if "frameClampingStartFrame" not in action: - action["frameClampingStartFrame"] = -1 - if "frameClampingEndFrame" not in action: - action["frameClampingEndFrame"] = -1 - if "numExtraJoints" not in action: - action["numExtraJoints"] = 0 - if "numeExtraTracks" not in action: - action["numeExtraTracks"] = 0 - if "constTrackKeys" not in action: - action["constTrackKeys"] = [] - if "trackKeys" not in action: - action["trackKeys"] = [] - if "fallbackFrameIndices" not in action: - action["fallbackFrameIndices"] = [] - if "optimizationHints" not in action: - action["optimizationHints"] = { "preferSIMD": False, "maxRotationCompression": 1} - - #if the export poses value is True, set the export options to ensure the armature is exported properly with the animations - options = default_cp77_options() - options.update(pose_export_options()) - for armature in armatures: - reset_armature(armature, context) - print(options) - bpy.ops.export_scene.gltf(filepath=filepath, use_selection=True, **options) - # TODO should that be here? - return{'FINISHED'} - - return {'FINISHED'} - - if not limit_selected: - for obj in bpy.data.objects: - if obj.type == 'MESH' and not "Icosphere" in obj.name: - obj.select_set(True) + + export_anims(context, filepath, options, armatures) + return{'FINISHED'} #if export_poses option isn't used, check to make sure there are meshes selected and throw an error if not - meshes = [obj for obj in objects if obj.type == 'MESH' and not "Icosphere" in obj.name] #throw an error in the message box if you haven't selected a mesh to export if not export_poses: + meshes = [obj for obj in objects if obj.type == 'MESH' and not "Icosphere" in obj.name] if not meshes: - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message="No meshes selected, please select at least one mesh") + show_message("No meshes selected, please select at least one mesh") return {'CANCELLED'} + export_meshes(context, filepath, export_visible, limit_selected, static_prop, red_garment_col, apply_transform, meshes, options) + +def export_anims(context, filepath, options, armatures): + for action in bpy.data.actions: + if "schema" not in action: + action["schema"] ={"type": "wkit.cp2077.gltf.anims","version": 4} + if "animationType" not in action: + action["animationType"] = 'Normal' + if "rootMotionType" not in action: + action["rootMotionType"] = 'Unknown' + if "frameClamping" not in action: + action["frameClamping"] = True + if "frameClampingStartFrame" not in action: + action["frameClampingStartFrame"] = -1 + if "frameClampingEndFrame" not in action: + action["frameClampingEndFrame"] = -1 + if "numExtraJoints" not in action: + action["numExtraJoints"] = 0 + if "numeExtraTracks" not in action: + action["numeExtraTracks"] = 0 + if "constTrackKeys" not in action: + action["constTrackKeys"] = [] + if "trackKeys" not in action: + action["trackKeys"] = [] + if "fallbackFrameIndices" not in action: + action["fallbackFrameIndices"] = [] + if "optimizationHints" not in action: + action["optimizationHints"] = { "preferSIMD": False, "maxRotationCompression": 1} + + options.update(pose_export_options()) + for armature in armatures: + reset_armature(armature, context) + print(options) + bpy.ops.export_scene.gltf(filepath=filepath, use_selection=True, **options) + # TODO should that be here? + return{'FINISHED'} + return {'FINISHED'} - #check that meshes include UVs and have less than 65000 verts, throw an error if not - for mesh in meshes: +def export_meshes(context, filepath, export_visible, limit_selected, static_prop, red_garment_col, apply_transform, meshes, options): + groupless_bones = set() + bone_names = [] + options.update(cp77_mesh_options()) + if not limit_selected: + for obj in bpy.data.objects: + if obj.type == 'MESH' and not "Icosphere" in obj.name: + obj.select_set(True) + for mesh in meshes: # apply transforms if apply_transform: bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) if not mesh.data.uv_layers: - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message="Meshes must have UV layers in order to import in Wolvenkit. See https://tinyurl.com/uv-layers") + show_message("Meshes must have UV layers in order to import in Wolvenkit. See https://tinyurl.com/uv-layers") return {'CANCELLED'} #check submesh vertex count to ensure it's less than the maximum for import vert_count = len(mesh.data.vertices) if vert_count > 65535: - message=(f"{mesh.name} has {vert_count} vertices. Each submesh must have less than 65,535 vertices. See https://tinyurl.com/vertex-count") - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=message) + show_message(f"{mesh.name} has {vert_count} vertices. Each submesh must have less than 65,535 vertices. See https://tinyurl.com/vertex-count") return {'CANCELLED'} #check that faces are triangulated, cancel export, switch to edit mode with the untriangulated faces selected and throw an error @@ -189,8 +188,11 @@ def export_cyberpunk_glb(context, filepath, export_poses=False, export_visible=F bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode(type='FACE') bpy.ops.mesh.select_face_by_sides(number=3, type='NOTEQUAL', extend=False) - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message="All faces must be triangulated before exporting. Untriangulated faces have been selected for you. See https://tinyurl.com/triangulate-faces") + show_message("All faces must be triangulated before exporting. Untriangulated faces have been selected for you. See https://tinyurl.com/triangulate-faces") return {'CANCELLED'} + + if red_garment_col: + add_garment_cap(mesh) # Check for ungrouped vertices, if they're found, switch to edit mode and select them # No need to do this for static props @@ -201,105 +203,80 @@ def export_cyberpunk_glb(context, filepath, export_poses=False, export_visible=F bpy.ops.mesh.select_mode(type='VERT') try: bpy.ops.mesh.select_ungrouped() - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=f"Ungrouped vertices found and selected in: {mesh.name}. See https://tinyurl.com/ungrouped-vertices") + show_message(f"Ungrouped vertices found and selected in: {mesh.name}. See https://tinyurl.com/ungrouped-vertices") except RuntimeError: - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=f"No vertex groups in: {mesh.name} are assigned weights. Assign weights before exporting. See https://tinyurl.com/assign-vertex-weights") + show_message(f"No vertex groups in: {mesh.name} are assigned weights. Assign weights before exporting. See https://tinyurl.com/assign-vertex-weights") return {'CANCELLED'} - if red_garment_col: - add_garment_cap(mesh) - - # set the export options for meshes - options = default_cp77_options() - options.update(cp77_mesh_options()) + armature_modifier = None + for modifier in mesh.modifiers: + if modifier.type == 'ARMATURE' and modifier.object: + armature_modifier = modifier + break - #print the options to the console - print(options) - - # if exporting meshes, iterate through any connected armatures, store their current state. if hidden, unhide them and select them for export - armature_states = {} + if not armature_modifier: + show_message((f"Armature missing from: {mesh.name} Armatures are required for movement. If this is intentional, try 'export as static prop'. See https://tinyurl.com/armature-missing")) + return {'CANCELLED'} + + armature = armature_modifier.object + + # Make necessary to armature visibility and selection state for export + armature.hide_set(False) + armature.select_set(True) + + for bone in armature.pose.bones: + bone_names.append(bone.name) + + if armature_modifier.object != mesh.parent: + armature_modifier.object = mesh.parent + + group_has_bone = {group.index: False for group in mesh.vertex_groups} + # groupless_bones = {} + for group in mesh.vertex_groups: + if group.name in bone_names: + group_has_bone[group.index] = True + # print(vertex_group.name) + + # Add groups with no weights to the set + for group_index, has_bone in group_has_bone.items(): + if not has_bone: + groupless_bones.add(mesh.vertex_groups[group_index].name) + + if len(groupless_bones) != 0: + bpy.ops.object.mode_set(mode='OBJECT') # Ensure in object mode for consistent behavior + groupless_bones_list = ", ".join(sorted(groupless_bones)) + armature.hide_set(True) + show_message((f"The following vertex groups are not assigned to a bone, this will result in blender creating a neutral_bone and cause Wolvenkit import to fail: {groupless_bones_list}\nSee https://tinyurl.com/unassigned-bone")) + return {'CANCELLED'} - if not static_prop: - for obj in objects: - if obj.type == 'MESH' and obj.select_get(): - armature_modifier = None - for modifier in obj.modifiers: - if modifier.type == 'ARMATURE' and modifier.object: - armature_modifier = modifier - break - - if not armature_modifier: - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=(f"Armature missing from: {obj.name} Armatures are required for movement. If this is intentional, try 'export as static prop'. See https://tinyurl.com/armature-missing")) - return {'CANCELLED'} - # Store original visibility and selection state - armature = armature_modifier.object - armature_states[armature] = {"hide": armature.hide_get(), - "select": armature.select_get()} - - # Make necessary to armature visibility and selection state for export - armature.hide_set(False) - armature.select_set(True) - - for bone in armature.pose.bones: - bone_names.append(bone.name) - - if armature_modifier.object != mesh.parent: - armature_modifier.object = mesh.parent - - group_has_bone = {group.index: False for group in obj.vertex_groups} - # groupless_bones = {} - for group in obj.vertex_groups: - if group.name in bone_names: - group_has_bone[group.index] = True - # print(vertex_group.name) - - # Add groups with no weights to the set - for group_index, has_bone in group_has_bone.items(): - if not has_bone: - groupless_bones.add(obj.vertex_groups[group_index].name) - - if len(groupless_bones) != 0: - bpy.ops.object.mode_set(mode='OBJECT') # Ensure in object mode for consistent behavior - groupless_bones_list = ", ".join(sorted(groupless_bones)) - armature.hide_set(True) - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=(f"The following vertex groups are not assigned to a bone, this will result in blender creating a neutral_bone and cause Wolvenkit import to fail: {groupless_bones_list}\nSee https://tinyurl.com/unassigned-bone")) - return {'CANCELLED'} + if mesh.data.name != mesh.name: + mesh.data.name = mesh.name - if mesh.data.name != mesh.name: - mesh.data.name = mesh.name + if limit_selected: + try: + bpy.ops.export_scene.gltf(filepath=filepath, use_selection=True, **options) + if not static_prop: + armature.hide_set(True) + except Exception as e: + print(e) - if limit_selected: + else: + if export_visible: try: - bpy.ops.export_scene.gltf(filepath=filepath, use_selection=True, **options) + bpy.ops.export_scene.gltf(filepath=filepath, use_visible=True, **options) if not static_prop: armature.hide_set(True) except Exception as e: print(e) else: - if export_visible: - try: - bpy.ops.export_scene.gltf(filepath=filepath, use_visible=True, **options) - if not static_prop: - armature.hide_set(True) - except Exception as e: - print(e) - - else: - try: - bpy.ops.export_scene.gltf(filepath=filepath, **options) - if not static_prop: - armature.hide_set(True) - except Exception as e: - print(e) - - - # Restore original armature visibility and selection states - # for armature, state in armature_states.items(): - # armature.select_set(state["select"]) - # armature.hide_set(state["hide"]) - - + try: + bpy.ops.export_scene.gltf(filepath=filepath, **options) + if not static_prop: + armature.hide_set(True) + except Exception as e: + print(e) + # def ExportAll(self, context): # #Iterate through all objects in the scene def ExportAll(self, context): From 95ad06e6bba882e02c57fc08425307651122d973 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:53:46 -0400 Subject: [PATCH 10/26] rotate now works properly the rotate function now properly applies to all selected objects instead of just the active one --- i_scene_cp77_gltf/main/bartmoss_functions.py | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index 209f680..3824354 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -20,16 +20,16 @@ def has_anims(o: bpy.types.Object) -> bool: return isinstance(o.data, bpy.types.Armature) and o.animation_data is not None def rotate_quat_180(self,context): - if context.active_object and context.active_object.rotation_quaternion: - active_obj = context.active_object - active_obj.rotation_mode = 'QUATERNION' + if context.selected_objects is not None: + for obj in context.selected_objects: + obj.rotation_mode = 'QUATERNION' - rotation_quat = Quaternion((0, 0, 1), radians(180)) - active_obj.rotation_quaternion = rotation_quat @ active_obj.rotation_quaternion - bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) - # Update the object to reflect the changes - active_obj.update_tag() - active_obj.update_from_editmode() + rotation_quat = Quaternion((0, 0, 1), radians(180)) + obj.rotation_quaternion = rotation_quat @ obj.rotation_quaternion + bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) + # Update the object to reflect the changes + obj.update_tag() + obj.update_from_editmode() # Update the scene to see the changes bpy.context.view_layer.update() @@ -70,22 +70,22 @@ def getShapeKeyName(obj): def getShapeKeyProps(obj): props = {} - + if hasShapeKeys(obj): for prop in obj.data.shape_keys.key_blocks: props[prop.name] = prop.value - + return props # returns a list of the given objects custom properties. def getCustomProps(obj): props = [] - + for prop in obj.keys(): if prop not in '_RNA_UI' and isinstance(obj[prop], (int, float, list, idprop.types.IDPropertyArray)): props.append(prop) - + return props # returns a list of modifiers for the given object @@ -132,9 +132,9 @@ def UV_by_bounds(selected_objects): me = obj.data bpy.ops.object.mode_set(mode='EDIT', toggle=False) bm = bmesh.from_edit_mesh(me) - + uv_layer = bm.loops.layers.uv.verify() - + # adjust uv coordinates for face in bm.faces: for loop in face.loops: @@ -144,5 +144,4 @@ def UV_by_bounds(selected_objects): loop_uv.uv[1]=(loop.vert.co.y-min_vertex[1])/(max_vertex[1]-min_vertex[1]) bmesh.update_edit_mesh(me) - bpy.ops.object.mode_set(mode=current_mode) - \ No newline at end of file + bpy.ops.object.mode_set(mode=current_mode) \ No newline at end of file From 78dfa2269ad94bd1c69af6c06f4e1042f08eeaff Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:54:27 -0400 Subject: [PATCH 11/26] remove whitespace --- i_scene_cp77_gltf/meshtools/meshtools.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 97e7a32..8a978b1 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -44,7 +44,6 @@ def CP77SubPrep(self, context, smooth_factor, merge_distance): bpy.ops.cp77.message_box('INVOKE_DEFAULT', message=f"Submesh preparation complete. {merged_vertices} verts merged") if context.mode != current_mode: bpy.ops.object.mode_set(mode=current_mode) - def CP77ArmatureSet(self, context): selected_meshes = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] @@ -139,7 +138,6 @@ def CP77UvChecker(self, context): return {'FINISHED'} - def CP77UvUnChecker(self, context): selected_meshes = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] current_mode = context.mode @@ -163,7 +161,6 @@ def CP77UvUnChecker(self, context): if context.mode != current_mode: bpy.ops.object.mode_set(mode=current_mode) - def CP77RefitChecker(self, context): scene = context.scene objects = scene.objects @@ -178,7 +175,6 @@ def CP77RefitChecker(self, context): print('refitter result:', refitter) return refitter - def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): selected_meshes = [obj for obj in context.selected_objects if obj.type == 'MESH'] scene = context.scene @@ -274,4 +270,4 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): lattice_modifier = mesh.modifiers.new(new_lattice.name,'LATTICE') for mesh in selected_meshes: print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) - lattice_modifier.object = new_lattice \ No newline at end of file + lattice_modifier.object = new_lattice From 1364cec5d91120ac80b40abc5166066b103b6fc7 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 00:47:52 -0400 Subject: [PATCH 12/26] fix vert colour functions --- i_scene_cp77_gltf/cyber_props.py | 15 ++- i_scene_cp77_gltf/main/common.py | 21 +++ i_scene_cp77_gltf/meshtools/__init__.py | 120 +++++++++++------- i_scene_cp77_gltf/meshtools/meshtools.py | 2 +- .../resources/vertex_color_presets.json | 12 +- 5 files changed, 118 insertions(+), 52 deletions(-) diff --git a/i_scene_cp77_gltf/cyber_props.py b/i_scene_cp77_gltf/cyber_props.py index 7eeed97..6c2dc53 100644 --- a/i_scene_cp77_gltf/cyber_props.py +++ b/i_scene_cp77_gltf/cyber_props.py @@ -4,7 +4,7 @@ from bpy.props import (StringProperty, EnumProperty, BoolProperty, CollectionProperty, FloatProperty, IntProperty, PointerProperty) from .main.physmat_lib import physmat_list #from . meshtools import (CP77CollectionList) -from .main.common import get_classes, get_rig_dir, get_refit_dir, get_resources_dir +from .main.common import get_classes, get_rig_dir, get_refit_dir, get_resources_dir, update_presets_items import sys resources_dir = get_resources_dir() @@ -15,6 +15,7 @@ def CP77RefitList(context): + target_addon_paths = [None] target_addon_names = ['None'] @@ -41,6 +42,8 @@ def CP77RefitList(context): # Return the list of tuples return target_body_paths, target_body_names +#def VertColourPresetList + def SetCyclesRenderer(use_cycles=True, set_gi_params=False): # set the render engine for all scenes to Cycles @@ -101,6 +104,7 @@ def CP77ArmatureList(self, context): print(f"Error accessing bpy.data.objects: {e}") arms = [] return arms + class CP77_PT_PanelProps(PropertyGroup): # collision panel props: @@ -113,7 +117,12 @@ class CP77_PT_PanelProps(PropertyGroup): ], default='VEHICLE' ) - + + vertex_color_presets: EnumProperty( + name="Vertex Color Preset", + items=lambda self, context: update_presets_items() or [(name, name, "") for name in get_colour_presets().keys()] + ) + physics_material: EnumProperty( items= enum_items, name="Physics Material", @@ -301,7 +310,7 @@ def register_props(): for cls in other_classes: bpy.utils.register_class(cls) Scene.cp77_panel_props = PointerProperty(type=CP77_PT_PanelProps) - + update_presets_items() def unregister_props(): for cls in reversed(other_classes): diff --git a/i_scene_cp77_gltf/main/common.py b/i_scene_cp77_gltf/main/common.py index 203a565..9494a9b 100644 --- a/i_scene_cp77_gltf/main/common.py +++ b/i_scene_cp77_gltf/main/common.py @@ -2,6 +2,7 @@ import bpy import os import math +from bpy.types import EnumProperty from mathutils import Color import pkg_resources import bmesh @@ -618,3 +619,23 @@ def createHash12Group(): CurMat.links.new(frac2.outputs[0],GroupOutput.inputs[0]) return CurMat +res_dir= get_resources_dir() + +# Path to the JSON file +VCOL_PRESETS_JSON = os.path.join(res_dir, "vertex_color_presets.json") + +def get_color_presets(): + if os.path.exists(VCOL_PRESETS_JSON): + with open(VCOL_PRESETS_JSON, 'r') as file: + return json.load(file) + return {} + +def save_presets(presets): + with open(VCOL_PRESETS_JSON, 'w') as file: + json.dump(presets, file, indent=4) + update_presets_items() + +def update_presets_items(): + presets = get_color_presets() + items = [(name, name, "") for name in presets.keys()] + return items diff --git a/i_scene_cp77_gltf/meshtools/__init__.py b/i_scene_cp77_gltf/meshtools/__init__.py index 9b9f264..d3b3cf5 100644 --- a/i_scene_cp77_gltf/meshtools/__init__.py +++ b/i_scene_cp77_gltf/meshtools/__init__.py @@ -5,7 +5,7 @@ from .meshtools import * from .verttools import * from ..main.bartmoss_functions import * -from ..main.common import get_classes +from ..main.common import get_classes, get_color_presets, save_presets from bpy.props import (StringProperty, EnumProperty) from bpy.types import (Scene, Operator, Panel) from ..cyber_props import CP77RefitList @@ -35,7 +35,26 @@ def draw(self, context): cp77_addon_prefs = bpy.context.preferences.addons['i_scene_cp77_gltf'].preferences if cp77_addon_prefs.show_modtools: - if cp77_addon_prefs.show_meshtools: + if cp77_addon_prefs.show_meshtools: + box.label(icon_value=get_icon("REFIT"), text="AKL Autofitter:") + row = box.row(align=True) + split = row.split(factor=0.29,align=True) + split.label(text="Shape:") + split.prop(props, 'refit_json', text="") + row = box.row(align=True) + row.operator("cp77.auto_fitter", text="Refit Selected Mesh") + row.prop(props, 'fbx_rot', text="", icon='LOOP_BACK', toggle=1) + + box = layout.box() + box.label(icon_value=get_icon("TECH"), text="Modifiers:") + row = box.row(align=True) + split = row.split(factor=0.35,align=True) + split.label(text="Target:") + split.prop(props, "selected_armature", text="") + row = box.row(align=True) + row.operator("cp77.set_armature", text="Change Armature Target") + + box = layout.box() box.label(text="Mesh Cleanup", icon_value=get_icon("TRAUMA")) row = box.row(align=True) split = row.split(factor=0.7,align=True) @@ -51,8 +70,7 @@ def draw(self, context): row.operator("cp77.group_verts", text="Group Ungrouped Verts") row = box.row(align=True) row.operator('object.delete_unused_vgroups', text="Delete Unused Vert Groups") - row = box.row(align=True) - row.operator("cp77.rotate_obj") + box = layout.box() box.label(icon_value=get_icon("SCULPT"), text="Modelling:") row = box.row(align=True) @@ -70,35 +88,43 @@ def draw(self, context): split.prop(props, "mesh_target", text="") row = box.row(align=True) box.operator("cp77.trans_weights", text="Transfer Vertex Weights") - box = layout.box() - box.label(icon_value=get_icon("REFIT"), text="AKL Autofitter:") - row = box.row(align=True) - split = row.split(factor=0.29,align=True) - split.label(text="Shape:") - split.prop(props, 'refit_json', text="") row = box.row(align=True) - row.operator("cp77.auto_fitter", text="Refit Selected Mesh") - row.prop(props, 'fbx_rot', text="", icon='LOOP_BACK', toggle=1) + row.operator("cp77.rotate_obj") + box = layout.box() - box.label(icon_value=get_icon("TECH"), text="Modifiers:") + box.label(text="Vertex Colours", icon="BRUSH_DATA") row = box.row(align=True) - split = row.split(factor=0.35,align=True) - split.label(text="Target:") - split.prop(props, "selected_armature", text="") - row = box.row(align=True) - row.operator("cp77.set_armature", text="Change Armature Target") - box = layout.box() - box.label(text="Vertex Colours", icon="MATERIAL") + split = row.split(factor=0.275,align=True) + split.label(text="Preset:") + split.prop(props, "vertex_color_presets", text="") + box.operator("cp77.apply_vertex_color_preset") box.operator("cp77.add_vertex_color_preset") - box.operator("cp77.apply_vertex_color_preset") - box.prop(context.scene, "cp77_vertex_color_preset", text="Select Preset") + box.operator("cp77.delete_vertex_color_preset") + box = layout.box() box.label(text="Material Export", icon="MATERIAL") box.operator("export_scene.hp") box.operator("export_scene.mlsetup") +class CP77DeleteVertexcolorPreset(Operator): + bl_idname = "cp77.delete_vertex_color_preset" + bl_label = "Delete Preset" -class CP77AddVertexColorPreset(Operator): + def execute(self, context): + props = context.scene.cp77_panel_props + preset_name = props.vertex_color_presets + presets = get_color_presets() + if preset_name in presets: + del presets[preset_name] + save_presets(presets) + self.report({'INFO'}, f"Preset '{preset_name}' deleted.") + else: + self.report({'ERROR'}, f"Preset '{preset_name}' not found.") + return {'CANCELLED'} + + return {'FINISHED'} + +class CP77AddVertexcolorPreset(Operator): bl_idname = "cp77.add_vertex_color_preset" bl_label = "Save Vertex Color Preset" bl_parent_id = "CP77_PT_MeshTools" @@ -108,12 +134,12 @@ class CP77AddVertexColorPreset(Operator): name="Color", subtype='COLOR', min=0.0, max=1.0, - size=3, - default=(1, 1, 1) + size=4, + default=(1, 1, 1, 1) # Include alpha in default ) def execute(self, context): - presets = get_colour_presets() + presets = get_color_presets() presets[self.preset_name] = list(self.color) save_presets(presets) self.report({'INFO'}, f"Preset '{self.preset_name}' added.") @@ -121,11 +147,12 @@ def execute(self, context): def invoke(self, context, event): tool_settings = context.tool_settings.vertex_paint - self.color = mathutils.Color.from_scene_linear_to_srgb(tool_settings.brush.color) + color = tool_settings.brush.color + alpha = tool_settings.brush.strength # Assuming alpha can be taken from brush strength + self.color = (*color[:3], alpha) # Combine color and alpha print(self.color) return context.window_manager.invoke_props_dialog(self) - class CP77WeightTransfer(Operator): bl_idname = 'cp77.trans_weights' bl_label = "Transfer weights from one mesh to another" @@ -138,18 +165,26 @@ def execute(self, context): return {"FINISHED"} # Operator to apply a preset -class CP77ApplyVertexColorPreset(Operator): +class CP77ApplyVertexcolorPreset(Operator): bl_idname = "cp77.apply_vertex_color_preset" - bl_label = "Apply Vertex Color Preset" + bl_label = "Apply Vertex color Preset" def execute(self, context): - preset = Scene.cp77_vertex_color_preset - if not preset: - self.report({'ERROR'}, f"Preset '{self.preset_name}' not found.") + props = context.scene.cp77_panel_props + preset_name = props.vertex_color_presets + if not preset_name: + self.report({'ERROR'}, "No preset selected.") + return {'CANCELLED'} + + presets = get_color_presets() + preset_color = presets.get(preset_name) + if not preset_color: + self.report({'ERROR'}, f"Preset '{preset_name}' not found.") return {'CANCELLED'} - preset_color = preset.append(1.0) # Adding alpha value initial_mode = context.mode + if initial_mode != 'OBJECT': + bpy.ops.object.mode_set(mode='OBJECT') for obj in context.selected_objects: if obj.type != 'MESH': @@ -162,27 +197,26 @@ def execute(self, context): color_layer = mesh.vertex_colors.active.data if initial_mode == 'EDIT_MESH': - bpy.ops.object.mode_set(mode='OBJECT') selected_verts = {v.index for v in mesh.vertices if v.select} - bpy.ops.object.mode_set(mode='EDIT') for poly in mesh.polygons: for loop_index in poly.loop_indices: loop_vert_index = mesh.loops[loop_index].vertex_index if loop_vert_index in selected_verts: color_layer[loop_index].color = preset_color - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='EDIT') + # bpy.ops.object.mode_set(mode='OBJECT') else: for poly in mesh.polygons: for loop_index in poly.loop_indices: loop_vert_index = mesh.loops[loop_index].vertex_index if mesh.vertices[loop_vert_index].select: color_layer[loop_index].color = preset_color - + bpy.ops.object.mode_set(mode=initial_mode) mesh.update() - - bpy.ops.object.mode_set(mode=initial_mode) - self.report({'INFO'}, f"Preset '{self.preset_name}' applied.") + + + self.report({'INFO'}, f"Preset '{preset_name}' applied.") return {'FINISHED'} @@ -286,7 +320,6 @@ def register_meshtools(): for cls in other_classes: if not hasattr(bpy.types, cls.__name__): bpy.utils.register_class(cls) - Scene.cp77_vertex_color_preset = EnumProperty(name="Vertex Color Preset", items=update_presets_items()) def unregister_meshtools(): for cls in reversed(other_classes): @@ -294,5 +327,4 @@ def unregister_meshtools(): bpy.utils.unregister_class(cls) for cls in reversed(operators): if hasattr(bpy.types, cls.__name__): - bpy.utils.unregister_class(cls) - del Scene.cp77_vertex_color_preset \ No newline at end of file + bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 8a978b1..763036c 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -270,4 +270,4 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): lattice_modifier = mesh.modifiers.new(new_lattice.name,'LATTICE') for mesh in selected_meshes: print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) - lattice_modifier.object = new_lattice + lattice_modifier.object = new_lattice \ No newline at end of file diff --git a/i_scene_cp77_gltf/resources/vertex_color_presets.json b/i_scene_cp77_gltf/resources/vertex_color_presets.json index 5929da8..3212e12 100644 --- a/i_scene_cp77_gltf/resources/vertex_color_presets.json +++ b/i_scene_cp77_gltf/resources/vertex_color_presets.json @@ -2,21 +2,25 @@ "Primary Taillight": [ 0.662745, 0.003922, - 0.003922 + 0.003922, + 1.0 ], "Secondary Taillight": [ 0.905882, 0.003922, - 0.0 + 0.0, + 1.0 ], "Primary Headlight": [ 0.486275, 0.003922, - 0.003922 + 0.003922, + 1.0 ], "Marker Light": [ 0.796079, 0.003922, - 0.003922 + 0.003922, + 1.0 ] } \ No newline at end of file From ad8276d4d40dea4f56267ee1151360443c97c88f Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 00:48:01 -0400 Subject: [PATCH 13/26] remove whitespace --- i_scene_cp77_gltf/main/setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/i_scene_cp77_gltf/main/setup.py b/i_scene_cp77_gltf/main/setup.py index 6678527..f136085 100644 --- a/i_scene_cp77_gltf/main/setup.py +++ b/i_scene_cp77_gltf/main/setup.py @@ -45,9 +45,6 @@ def create(self,materialIndex): if self.obj.get("Materials"): rawMat = self.obj["Materials"][materialIndex] - - - verbose=True bpyMat = bpy.data.materials.new(rawMat["Name"]) From be1dcab04c93b030d66e36a5948dd846f324d444 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:06:32 -0400 Subject: [PATCH 14/26] add missing json types add .mt, .mi and cfolliage json loading to jsontool --- i_scene_cp77_gltf/jsontool.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/i_scene_cp77_gltf/jsontool.py b/i_scene_cp77_gltf/jsontool.py index a278c2c..a182c34 100644 --- a/i_scene_cp77_gltf/jsontool.py +++ b/i_scene_cp77_gltf/jsontool.py @@ -119,6 +119,24 @@ def jsonload(filepath): show_message(f"invalid mltemplate.json: {base_name} import will continue but shaders may be incorrectly setup for this mesh") # Do something for .mlsetup.json + case _ if base_name.endswith('.mt.json'): + if not cp77_addon_prefs.non_verbose: + print(f"Processing: {base_name}") + data=load_json(filepath) + if json_ver_validate(data) == False: + if not cp77_addon_prefs.non_verbose: + print(f"invalid mt.json found at: {filepath} import will continue but shaders may be incorrectly set up for this mesh") + show_message(f"invalid mt.json: {base_name} import will continue but shaders may be incorrectly setup for this mesh") + + case _ if base_name.endswith('.mi.json'): + if not cp77_addon_prefs.non_verbose: + print(f"Processing: {base_name}") + data=load_json(filepath) + if json_ver_validate(data) == False: + if not cp77_addon_prefs.non_verbose: + print(f"invalid mi.json found at: {filepath} import will continue but shaders may be incorrectly set up for this mesh") + show_message(f"invalid mi.json: {base_name} import will continue but shaders may be incorrectly setup for this mesh") + case _ if base_name.endswith('.phys.json'): if not cp77_addon_prefs.non_verbose: print(f"Processing: {base_name}") @@ -163,6 +181,16 @@ def jsonload(filepath): if not cp77_addon_prefs.non_verbose: print(f"invalid hp.json found at: {filepath} this plugin requires jsons generated with the latest version of Wolvenkit") show_message(f"invalid Hair Profile: {base_name} this plugin requires jsons generated with the latest version of Wolvenkit") + + case _ if base_name.endswith('.cfoliage.json'): + if not cp77_addon_prefs.non_verbose: + print(f"Processing: {base_name}") + data=load_json(filepath) + if json_ver_validate(data) == False: + if not cp77_addon_prefs.non_verbose: + print(f"invalid cfoliage.json found at: {filepath} this plugin requires jsons generated with the latest version of Wolvenkit") + show_message(f"invalid cfoliage.json : {base_name} this plugin requires jsons generated with the latest version of Wolvenkit") + case _: if not cp77_addon_prefs.non_verbose: print(f"Incompatible Json: {base_name}") From 443ba2bd2b61b6025f1e2a303a50e49400232606 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:08:12 -0400 Subject: [PATCH 15/26] sector import jsontool usage now loads all jsons using jsontool --- i_scene_cp77_gltf/importers/sector_import.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/i_scene_cp77_gltf/importers/sector_import.py b/i_scene_cp77_gltf/importers/sector_import.py index a030fe4..0bb2fed 100644 --- a/i_scene_cp77_gltf/importers/sector_import.py +++ b/i_scene_cp77_gltf/importers/sector_import.py @@ -15,7 +15,7 @@ # 3) If you want it to generate the _new collections for you to add new stuff in set am_modding to True # 4) Run it -import json +from ..jsontool import jsonload import glob import os import bpy @@ -278,8 +278,7 @@ def importSectors( filepath='', want_collisions=False, am_modding=False, with_ma if VERBOSE: print(os.path.join(path,os.path.basename(project)+'.streamingsector.json')) print(filepath) - with open(filepath,'r') as f: - j=json.load(f) + j = jsonload(filepath) sectorName=os.path.basename(filepath)[:-5] t=j['Data']['RootChunk']['nodeData']['Data'] nodes = j["Data"]["RootChunk"]["nodes"] @@ -400,8 +399,7 @@ def importSectors( filepath='', want_collisions=False, am_modding=False, with_ma if VERBOSE: print(projectjson) print(filepath) - with open(filepath,'r') as f: - j=json.load(f) + j=jsonload(filepath) t=j['Data']['RootChunk']['nodeData']['Data'] # add nodeDataIndex props to all the nodes in t @@ -683,8 +681,7 @@ def importSectors( filepath='', want_collisions=False, am_modding=False, with_ma meshname = data['mesh']['DepotPath']['$value'].replace('\\', os.sep) foliageResource=data['foliageResource']['DepotPath']['$value'].replace('\\', os.sep)+'.json' if os.path.exists(os.path.join(path,foliageResource)): - with open(os.path.join(path,foliageResource),'r') as frfile: - frjson=json.load(frfile) + frjson=jsonload(os.path.join(path,foliageResource)) inst_pos=get_pos(inst) Bucketnum=data['populationSpanInfo']['cketCount'] Bucketstart=data['populationSpanInfo']['cketBegin'] @@ -798,8 +795,7 @@ def importSectors( filepath='', want_collisions=False, am_modding=False, with_ma jsonpath = os.path.join(path,mipath)+".json" #print(jsonpath) try: - with open(jsonpath,'r') as jsonpath: - obj=json.load(jsonpath) + obj=jsonload(jsonpath) index = 0 obj["Data"]["RootChunk"]['alpha'] = e['Data']['alpha'] #FIXME: image_format From c8405c99b4a21a078d5df725818811e2e4f57ff1 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:14:29 -0400 Subject: [PATCH 16/26] improve preferences ui --- i_scene_cp77_gltf/cyber_prefs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/i_scene_cp77_gltf/cyber_prefs.py b/i_scene_cp77_gltf/cyber_prefs.py index a28da11..e5a2759 100644 --- a/i_scene_cp77_gltf/cyber_prefs.py +++ b/i_scene_cp77_gltf/cyber_prefs.py @@ -68,19 +68,20 @@ class CP77IOSuitePreferences(AddonPreferences): def draw(self, context): layout = self.layout box = layout.box() - row = box.row() row.prop(self, "show_modtools",toggle=1) row.prop(self, "experimental_features",toggle=1) row.prop(self, "non_verbose",toggle=1) if self.experimental_features: + box = layout.box() + box.label(text="Material Depot Path:") row = box.row() - row.prop(self, "depotfolder_path") + row.prop(self, "depotfolder_path", text="") row = box.row() if self.show_modtools: row.alignment = 'LEFT' box = layout.box() - box.label(text="Mod Tools Properties") + box.label(text="Mod Tools Preferences") split = row.split(factor=0.5,align=True) col = split.column(align=True) row.alignment = 'LEFT' From c9fb49fc89fd7b38713e39259f09cc30cf2eb38e Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:19:24 -0400 Subject: [PATCH 17/26] phys import using jsontool --- i_scene_cp77_gltf/importers/phys_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i_scene_cp77_gltf/importers/phys_import.py b/i_scene_cp77_gltf/importers/phys_import.py index afac774..9ac6a86 100644 --- a/i_scene_cp77_gltf/importers/phys_import.py +++ b/i_scene_cp77_gltf/importers/phys_import.py @@ -1,4 +1,4 @@ -import json +from ..jsontool import jsonload import bpy import bmesh import time @@ -15,8 +15,7 @@ def cp77_phys_import(filepath, rig=None, chassis_z=None): if space.type == 'VIEW_3D': space.shading.wireframe_color_type = 'OBJECT' - with open(physJsonPath, 'r') as phys: - data = json.load(phys) + data = jsonload(physJsonPath) for index, i in enumerate(data['Data']['RootChunk']['bodies']): bname = (i['Data']['name']['$value']) @@ -92,5 +91,6 @@ def cp77_phys_import(filepath, rig=None, chassis_z=None): capsule.delta_location[2] = chassis_z bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) new_collection.objects.link(capsule) + if not cp77_addon_prefs.non_verbose: print(f"phys collider Import Time: {(time.time() - start_time)} Seconds") From 889e5bbeb438f152381298dbad733ae126472902 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:45:30 -0400 Subject: [PATCH 18/26] update wkit version in template jsons --- i_scene_cp77_gltf/resources/empty.streamingsector.json | 2 +- i_scene_cp77_gltf/resources/hair_profile_template.hp.json | 2 +- i_scene_cp77_gltf/resources/metal_base_template.mi.json | 2 +- i_scene_cp77_gltf/resources/template.streamingblock.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/i_scene_cp77_gltf/resources/empty.streamingsector.json b/i_scene_cp77_gltf/resources/empty.streamingsector.json index eb73d97..96d9c7e 100644 --- a/i_scene_cp77_gltf/resources/empty.streamingsector.json +++ b/i_scene_cp77_gltf/resources/empty.streamingsector.json @@ -1,6 +1,6 @@ { "Header": { - "WolvenKitVersion": "8.12.1-nightly.2024-01-08\u002Baf006c7d02fe4693a3d87822873ba322acd94c2d", + "WolvenKitVersion": "8.14.1-nightly.2024-07-21", "WKitJsonVersion": "0.0.8", "GameVersion": 2100, "ExportedDateTime": "2024-01-11T20:29:08.1394444Z", diff --git a/i_scene_cp77_gltf/resources/hair_profile_template.hp.json b/i_scene_cp77_gltf/resources/hair_profile_template.hp.json index 0a0d003..1885645 100644 --- a/i_scene_cp77_gltf/resources/hair_profile_template.hp.json +++ b/i_scene_cp77_gltf/resources/hair_profile_template.hp.json @@ -1,7 +1,7 @@ { "Header": { - "WolvenKitVersion": "8.11.1-nightly.2023-10-15", + "WolvenKitVersion": "8.14.1-nightly.2024-07-21", "WKitJsonVersion": "0.0.8", "GameVersion": 2000, "ExportedDateTime": "2023-10-16T08:30:59.0747987Z", diff --git a/i_scene_cp77_gltf/resources/metal_base_template.mi.json b/i_scene_cp77_gltf/resources/metal_base_template.mi.json index 04f586c..e5425dc 100644 --- a/i_scene_cp77_gltf/resources/metal_base_template.mi.json +++ b/i_scene_cp77_gltf/resources/metal_base_template.mi.json @@ -1,6 +1,6 @@ { "Header": { - "WolvenKitVersion": "8.11.1-nightly.2023-10-15", + "WolvenKitVersion": "8.14.1-nightly.2024-07-21", "WKitJsonVersion": "0.0.8", "GameVersion": 2000, "ExportedDateTime": "2023-10-16T08:30:59.0747987Z", diff --git a/i_scene_cp77_gltf/resources/template.streamingblock.json b/i_scene_cp77_gltf/resources/template.streamingblock.json index 989b6ac..33e9d3d 100644 --- a/i_scene_cp77_gltf/resources/template.streamingblock.json +++ b/i_scene_cp77_gltf/resources/template.streamingblock.json @@ -1,6 +1,6 @@ { "Header": { - "WolvenKitVersion": "8.11.2-nightly.2023-11-14", + "WolvenKitVersion": "8.14.1-nightly.2024-07-21", "WKitJsonVersion": "0.0.8", "GameVersion": 2020, "ExportedDateTime": "2023-11-21T07:26:32.2758491Z", From 4747678b2fe0e06f8b2451739b55328661dccb6f Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:09:31 -0400 Subject: [PATCH 19/26] handle cases where nothing is selected in meshtools functions --- i_scene_cp77_gltf/meshtools/__init__.py | 35 +++++++++--------- i_scene_cp77_gltf/meshtools/meshtools.py | 45 +++++++++++++++++++----- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/i_scene_cp77_gltf/meshtools/__init__.py b/i_scene_cp77_gltf/meshtools/__init__.py index d3b3cf5..ef15a6d 100644 --- a/i_scene_cp77_gltf/meshtools/__init__.py +++ b/i_scene_cp77_gltf/meshtools/__init__.py @@ -10,7 +10,6 @@ from bpy.types import (Scene, Operator, Panel) from ..cyber_props import CP77RefitList from ..icons.cp77_icons import get_icon -import mathutils class CP77_PT_MeshTools(Panel): bl_label = "Mesh Tools" @@ -53,23 +52,6 @@ def draw(self, context): split.prop(props, "selected_armature", text="") row = box.row(align=True) row.operator("cp77.set_armature", text="Change Armature Target") - - box = layout.box() - box.label(text="Mesh Cleanup", icon_value=get_icon("TRAUMA")) - row = box.row(align=True) - split = row.split(factor=0.7,align=True) - split.label(text="Merge Distance:") - split.prop(props,"merge_distance", text="", slider=True) - row = box.row(align=True) - split = row.split(factor=0.7,align=True) - split.label(text="Smooth Factor:") - split.prop(props,"smooth_factor", text="", slider=True) - row = box.row(align=True) - row.operator("cp77.submesh_prep") - row = box.row(align=True) - row.operator("cp77.group_verts", text="Group Ungrouped Verts") - row = box.row(align=True) - row.operator('object.delete_unused_vgroups', text="Delete Unused Vert Groups") box = layout.box() box.label(icon_value=get_icon("SCULPT"), text="Modelling:") @@ -91,6 +73,23 @@ def draw(self, context): row = box.row(align=True) row.operator("cp77.rotate_obj") + box = layout.box() + box.label(text="Mesh Cleanup", icon_value=get_icon("TRAUMA")) + row = box.row(align=True) + split = row.split(factor=0.7,align=True) + split.label(text="Merge Distance:") + split.prop(props,"merge_distance", text="", slider=True) + row = box.row(align=True) + split = row.split(factor=0.7,align=True) + split.label(text="Smooth Factor:") + split.prop(props,"smooth_factor", text="", slider=True) + row = box.row(align=True) + row.operator("cp77.submesh_prep") + row = box.row(align=True) + row.operator("cp77.group_verts", text="Group Ungrouped Verts") + row = box.row(align=True) + row.operator('object.delete_unused_vgroups', text="Delete Unused Vert Groups") + box = layout.box() box.label(text="Vertex Colours", icon="BRUSH_DATA") row = box.row(align=True) diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 763036c..4b9337f 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -10,10 +10,12 @@ def CP77SubPrep(self, context, smooth_factor, merge_distance): scn = context.scene obj = context.object current_mode = context.mode + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} if obj.type != 'MESH': - bpy.ops.cp77.message_box('INVOKE_DEFAULT', message="The active object is not a mesh.") - return {'CANCELLED'} - + show_message("The active object is not a mesh.") + return {'CANCELLED'} if current_mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode(type="EDGE") @@ -50,7 +52,12 @@ def CP77ArmatureSet(self, context): props = context.scene.cp77_panel_props target_armature_name = props.selected_armature target_armature = bpy.data.objects.get(target_armature_name) - + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} if len(selected_meshes) > 0: if target_armature and target_armature.type == 'ARMATURE': # Ensure the target armature has a collection @@ -87,10 +94,14 @@ def CP77ArmatureSet(self, context): def CP77UvChecker(self, context): selected_meshes = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] bpy_mats=bpy.data.materials - current_mode = context.mode - - - + obj = context.object + current_mode = context.mode + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} for mat in bpy_mats: if mat.name == 'UV_Checker': uvchecker = mat @@ -140,7 +151,15 @@ def CP77UvChecker(self, context): def CP77UvUnChecker(self, context): selected_meshes = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] + obj = context.object current_mode = context.mode + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} + uvchecker = 'UV_Checker' original_mat_name = None for mesh in selected_meshes: @@ -178,6 +197,14 @@ def CP77RefitChecker(self, context): def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): selected_meshes = [obj for obj in context.selected_objects if obj.type == 'MESH'] scene = context.scene + obj = context.object + current_mode = context.mode + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} refitter_obj = None r_c = None print(fbx_rot) @@ -270,4 +297,4 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): lattice_modifier = mesh.modifiers.new(new_lattice.name,'LATTICE') for mesh in selected_meshes: print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) - lattice_modifier.object = new_lattice \ No newline at end of file + lattice_modifier.object = new_lattice From 4d7078244ae8610cd346b070371ffcd5bcc1c47e Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:09:47 -0400 Subject: [PATCH 20/26] process gradient.json files --- i_scene_cp77_gltf/jsontool.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/i_scene_cp77_gltf/jsontool.py b/i_scene_cp77_gltf/jsontool.py index a182c34..6ee0eeb 100644 --- a/i_scene_cp77_gltf/jsontool.py +++ b/i_scene_cp77_gltf/jsontool.py @@ -98,7 +98,16 @@ def jsonload(filepath): if not cp77_addon_prefs.non_verbose: print('Building shaders') # Do something for .material.json - + + case _ if base_name.endswith('.gradient.json'): + if not cp77_addon_prefs.non_verbose: + print(f"Processing: {base_name}") + data=load_json(filepath) + if json_ver_validate(data) == False: + if not cp77_addon_prefs.non_verbose: + print(f"invalid gradient.json found at: {filepath} this plugin requires jsons generated using the latest version of Wolvenkit") + show_message(f"found invalid gradient.json: {base_name} this plugin requires jsons generated using the latest version of Wolvenkit") + case _ if base_name.endswith('.mlsetup.json'): if not cp77_addon_prefs.non_verbose: print(f"Processing: {base_name}") @@ -214,7 +223,7 @@ def openJSON(path, mode='r', ProjPath='', DepotPath=''): inproj=os.path.join(ProjPath,path) if os.path.exists(inproj): - file = open(inproj,mode) + data = jsonload(inproj) else: - file = open(os.path.join(DepotPath,path),mode) - return file \ No newline at end of file + data = jsonload(os.path.join(DepotPath,path)) + return data \ No newline at end of file From 4833ce51e0bdd4cfc1a434b933da31532e35bdd0 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:10:16 -0400 Subject: [PATCH 21/26] use jsontool for sector export --- i_scene_cp77_gltf/exporters/sectors_export.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/i_scene_cp77_gltf/exporters/sectors_export.py b/i_scene_cp77_gltf/exporters/sectors_export.py index 8277bc9..7e96a64 100644 --- a/i_scene_cp77_gltf/exporters/sectors_export.py +++ b/i_scene_cp77_gltf/exporters/sectors_export.py @@ -26,6 +26,7 @@ # - sort out instanced bits import json +from ..jsontool import jsonload import glob import os import bpy @@ -409,8 +410,7 @@ def exportSectors(filename, use_yaml): # Open the blank template streaming sector resourcepath=get_resources_dir() - with open(os.path.join(resourcepath,'empty.streamingsector.json'),'r') as f: - template_json=json.load(f) + template_json=jsonload(resourcepath,'empty.streamingsector.json') template_nodes = template_json["Data"]["RootChunk"]["nodes"] template_nodeData = template_json['Data']['RootChunk']['nodeData']['Data'] ID=0 @@ -441,8 +441,7 @@ def exportSectors(filename, use_yaml): print(filepath) if filepath==os.path.join(projpath,projectjson): continue - with open(filepath,'r') as f: - j=json.load(f) + j=jsonload(filepath) nodes = j["Data"]["RootChunk"]["nodes"] t=j['Data']['RootChunk']['nodeData']['Data'] # add nodeDataIndex props to all the nodes in t @@ -846,8 +845,7 @@ def exportSectors(filename, use_yaml): source_sect_coll=bpy.data.collections.get(source_sector) source_sect_json_path=source_sect_coll['filepath'] print(source_sect_json_path) - with open(source_sect_json_path,'r') as f: - source_sect_json=json.load(f) + source_sect_json=jsonload(source_sect_json_path) source_nodes = source_sect_json["Data"]["RootChunk"]["nodes"] print(len(source_nodes),col['nodeIndex']) print(source_nodes[col['nodeIndex']]) @@ -876,8 +874,7 @@ def exportSectors(filename, use_yaml): source_sect_coll=bpy.data.collections.get(source_sector) source_sect_json_path=source_sect_coll['filepath'] print(source_sect_json_path) - with open(source_sect_json_path,'r') as f: - source_sect_json=json.load(f) + source_sect_json=jsonload(source_sect_json_path) source_nodes = source_sect_json["Data"]["RootChunk"]["nodes"] nodes.append(copy.deepcopy(source_nodes[ni])) new_Index=len(nodes)-1 @@ -953,8 +950,7 @@ def exportSectors(filename, use_yaml): source_sect_coll=bpy.data.collections.get(source_sector) source_sect_json_path=source_sect_coll['filepath'] print(source_sect_json_path) - with open(source_sect_json_path,'r') as f: - source_sect_json=json.load(f) + source_sect_json = jsonload(source_sect_json_path) source_nodes = source_sect_json["Data"]["RootChunk"]["nodes"] nodes.append(copy.deepcopy(source_nodes[ni])) new_Index=len(nodes)-1 From d8a553a3bdd77ae245a0ad8c5dc22d326e0ff282 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:14:07 -0400 Subject: [PATCH 22/26] use jsontool for materials --- .../material_types/eyegradient.py | 11 ++-------- .../material_types/multilayered.py | 19 +++-------------- .../material_types/multilayeredTerrain.py | 21 +++++-------------- .../material_types/multilayeredclearcoat.py | 20 ++++-------------- 4 files changed, 14 insertions(+), 57 deletions(-) diff --git a/i_scene_cp77_gltf/material_types/eyegradient.py b/i_scene_cp77_gltf/material_types/eyegradient.py index 3a8987b..7ce0c73 100644 --- a/i_scene_cp77_gltf/material_types/eyegradient.py +++ b/i_scene_cp77_gltf/material_types/eyegradient.py @@ -1,8 +1,7 @@ import bpy import os from ..main.common import * -from ..jsontool import json_ver_validate, openJSON -import json +from ..jsontool import openJSON class EyeGradient: def __init__(self, BasePath,image_format, ProjPath): @@ -14,13 +13,7 @@ def create(self,Data,Mat): # load the gradient profile from the depot - file = openJSON(Data["IrisColorGradient"] + ".json",mode='r', DepotPath=self.BasePath, ProjPath=self.ProjPath) - profile = json.loads(file.read()) - file.close() - valid_json=json_ver_validate(profile) - if not valid_json: - self.report({'ERROR'}, "Incompatible eye gradient json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + profile = openJSON(Data["IrisColorGradient"] + ".json",mode='r', DepotPath=self.BasePath, ProjPath=self.ProjPath) profile= profile["Data"]["RootChunk"] CurMat = Mat.node_tree pBSDF = CurMat.nodes[loc('Principled BSDF')] diff --git a/i_scene_cp77_gltf/material_types/multilayered.py b/i_scene_cp77_gltf/material_types/multilayered.py index d1d4739..9453330 100644 --- a/i_scene_cp77_gltf/material_types/multilayered.py +++ b/i_scene_cp77_gltf/material_types/multilayered.py @@ -1,8 +1,7 @@ import bpy import os from ..main.common import * -import json -from ..jsontool import openJSON, json_ver_validate, jsonloads +from ..jsontool import openJSON def _getOrCreateLayerBlend(): @@ -268,13 +267,7 @@ def createLayerMaterial(self,LayerName,LayerCount,CurMat,mlmaskpath,normalimgpat def create(self,Data,Mat): Mat['MLSetup']= Data["MultilayerSetup"] - file = openJSON( Data["MultilayerSetup"] + ".json",mode='r',DepotPath=self.BasePath, ProjPath=self.ProjPath) - mlsetup = jsonloads(file.read()) - file.close() - valid_json=json_ver_validate(mlsetup) - if not valid_json: - self.report({'ERROR'}, "Incompatible mlsetup json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + mlsetup = openJSON( Data["MultilayerSetup"] + ".json",mode='r',DepotPath=self.BasePath, ProjPath=self.ProjPath) mlsetup = mlsetup["Data"]["RootChunk"] xllay = mlsetup.get("layers") if xllay is None: @@ -346,13 +339,7 @@ def create(self,Data,Mat): if Microblend != "null": MBI = imageFromPath(self.BasePath+Microblend,self.image_format,True) - file = openJSON( material + ".json",mode='r',DepotPath=self.BasePath, ProjPath=self.ProjPath) - mltemplate = jsonloads(file.read()) - file.close() - valid_json=json_ver_validate(mltemplate) - if not valid_json: - self.report({'ERROR'}, "Incompatible mltemplate json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + mltemplate = openJSON( material + ".json",mode='r',DepotPath=self.BasePath, ProjPath=self.ProjPath) mltemplate = mltemplate["Data"]["RootChunk"] OverrideTable = createOverrideTable(mltemplate)#get override info for colors and what not # Mat[os.path.basename(material).split('.')[0]+'_cols']=OverrideTable["ColorScale"] diff --git a/i_scene_cp77_gltf/material_types/multilayeredTerrain.py b/i_scene_cp77_gltf/material_types/multilayeredTerrain.py index 8995be9..f368b4a 100644 --- a/i_scene_cp77_gltf/material_types/multilayeredTerrain.py +++ b/i_scene_cp77_gltf/material_types/multilayeredTerrain.py @@ -1,8 +1,7 @@ import bpy import os from ..main.common import * -import json -from ..jsontool import json_ver_validate, openJSON +from ..jsontool import jsonload class MultilayeredTerrain: def __init__(self, BasePath,image_format,ProjPath): @@ -255,13 +254,8 @@ def createLayerMaterial(self,LayerName,LayerCount,CurMat,mlmaskpath,normalimgpat def create(self,Data,Mat): - file = open(self.BasePath + Data["MultilayerSetup"] + ".json",mode='r') - mlsetup = json.loads(file.read()) - file.close() - valid_json=json_ver_validate(mlsetup) - if not valid_json: - self.report({'ERROR'}, "Incompatible mlsetup json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + file = self.BasePath + Data["MultilayerSetup"] + ".json" + mlsetup = jsonload(file) mlsetup = mlsetup["Data"]["RootChunk"] xllay = mlsetup.get("layers") if xllay is None: @@ -323,13 +317,8 @@ def create(self,Data,Mat): MBI = imageFromRelPath(Microblend,self.image_format,True,self.BasePath,self.ProjPath) - file = open(self.BasePath + material + ".json",mode='r') - mltemplate = json.loads(file.read()) - file.close() - valid_json=json_ver_validate(mltemplate) - if not valid_json: - self.report({'ERROR'}, "Incompatible mltemplate json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + file = self.BasePath + material + ".json" + mltemplate = jsonload(file) mltemplate = mltemplate["Data"]["RootChunk"] OverrideTable = createOverrideTable(mltemplate)#get override info for colors and what not diff --git a/i_scene_cp77_gltf/material_types/multilayeredclearcoat.py b/i_scene_cp77_gltf/material_types/multilayeredclearcoat.py index a278dbd..edbf6de 100644 --- a/i_scene_cp77_gltf/material_types/multilayeredclearcoat.py +++ b/i_scene_cp77_gltf/material_types/multilayeredclearcoat.py @@ -1,8 +1,7 @@ import bpy import os from ..main.common import * -import json -from ..jsontool import openJSON, json_ver_validate +from ..jsontool import jsonload class MultilayeredClearCoat: def __init__(self, BasePath,image_format): @@ -273,13 +272,7 @@ def createLayerMaterial(self,LayerName,LayerCount,CurMat,mlmaskpath,normalimgpat def create(self,Data,Mat): - file = open(self.BasePath + Data["MultilayerSetup"] + ".json",mode='r') - mlsetup = json.loads(file.read()) - file.close() - valid_json=json_ver_validate(mlsetup) - if not valid_json: - self.report({'ERROR'}, "Incompatible mlsetup json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + mlsetup = jsonload((self.BasePath + Data["MultilayerSetup"] + ".json")) mlsetup = mlsetup["Data"]["RootChunk"] xllay = mlsetup.get("layers") if xllay is None: @@ -336,13 +329,8 @@ def create(self,Data,Mat): if Microblend != "null": MBI = imageFromPath(self.BasePath+Microblend,self.image_format,True) - file = open(self.BasePath + material + ".json",mode='r') - mltemplate = json.loads(file.read()) - file.close() - valid_json=json_ver_validate(mltemplate) - if not valid_json: - self.report({'ERROR'}, "Incompatible mltemplate json file detected. This add-on version requires materials generated WolvenKit 8.9.1 or higher.") - return + file = self.BasePath + material + ".json" + mltemplate = jsonload(file) mltemplate = mltemplate["Data"]["RootChunk"] OverrideTable = self.createOverrideTable(mltemplate)#get override info for colors and what not From de469133f8ffda59ff309f8d4ab39fdcb3ae4a0f Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:20:23 -0400 Subject: [PATCH 23/26] minor meshtools updates --- i_scene_cp77_gltf/main/bartmoss_functions.py | 15 +++---- i_scene_cp77_gltf/meshtools/__init__.py | 7 ++++ i_scene_cp77_gltf/meshtools/meshtools.py | 1 + i_scene_cp77_gltf/meshtools/verttools.py | 41 +++++++------------- 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index 3824354..f18ef21 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -70,22 +70,22 @@ def getShapeKeyName(obj): def getShapeKeyProps(obj): props = {} - + if hasShapeKeys(obj): for prop in obj.data.shape_keys.key_blocks: props[prop.name] = prop.value - + return props # returns a list of the given objects custom properties. def getCustomProps(obj): props = [] - + for prop in obj.keys(): if prop not in '_RNA_UI' and isinstance(obj[prop], (int, float, list, idprop.types.IDPropertyArray)): props.append(prop) - + return props # returns a list of modifiers for the given object @@ -132,9 +132,9 @@ def UV_by_bounds(selected_objects): me = obj.data bpy.ops.object.mode_set(mode='EDIT', toggle=False) bm = bmesh.from_edit_mesh(me) - + uv_layer = bm.loops.layers.uv.verify() - + # adjust uv coordinates for face in bm.faces: for loop in face.loops: @@ -144,4 +144,5 @@ def UV_by_bounds(selected_objects): loop_uv.uv[1]=(loop.vert.co.y-min_vertex[1])/(max_vertex[1]-min_vertex[1]) bmesh.update_edit_mesh(me) - bpy.ops.object.mode_set(mode=current_mode) \ No newline at end of file + bpy.ops.object.mode_set(mode=current_mode) + \ No newline at end of file diff --git a/i_scene_cp77_gltf/meshtools/__init__.py b/i_scene_cp77_gltf/meshtools/__init__.py index ef15a6d..7f13119 100644 --- a/i_scene_cp77_gltf/meshtools/__init__.py +++ b/i_scene_cp77_gltf/meshtools/__init__.py @@ -171,6 +171,13 @@ class CP77ApplyVertexcolorPreset(Operator): def execute(self, context): props = context.scene.cp77_panel_props preset_name = props.vertex_color_presets + obj = context.object + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} if not preset_name: self.report({'ERROR'}, "No preset selected.") return {'CANCELLED'} diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 4b9337f..0467d4a 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -52,6 +52,7 @@ def CP77ArmatureSet(self, context): props = context.scene.cp77_panel_props target_armature_name = props.selected_armature target_armature = bpy.data.objects.get(target_armature_name) + obj = context.object if not obj: show_message("No active object. Please Select a Mesh and try again") return {'CANCELLED'} diff --git a/i_scene_cp77_gltf/meshtools/verttools.py b/i_scene_cp77_gltf/meshtools/verttools.py index 41380b2..7f874f2 100644 --- a/i_scene_cp77_gltf/meshtools/verttools.py +++ b/i_scene_cp77_gltf/meshtools/verttools.py @@ -8,32 +8,15 @@ res_dir = get_resources_dir() -script_dir = get_script_dir() - - -# Path to the JSON file -VCOL_PRESETS_JSON = os.path.join(res_dir, "vertex_color_presets.json") - - -def get_colour_presets(): - if os.path.exists(VCOL_PRESETS_JSON): - with open(VCOL_PRESETS_JSON, 'r') as file: - return json.load(file) - return {} - - -def save_presets(presets): - with open(VCOL_PRESETS_JSON, 'w') as file: - json.dump(presets, file, indent=4) - - -def update_presets_items(): - presets = get_colour_presets() - return [(name, name, "") for name in presets.keys()] - def del_empty_vgroup(self, context): - obj = bpy.context + obj = context.object + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} + if obj.type != 'MESH': + show_message("The active object is not a mesh.") + return {'CANCELLED'} try: for obj in bpy.context.selected_objects: groups = {r: None for r in range(len(obj.vertex_groups))} @@ -47,10 +30,12 @@ def del_empty_vgroup(self, context): print(f"encountered the following error:") print(e) - def find_nearest_vertex_group(obj, vertex): min_distance = math.inf nearest_vertex = None + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} if obj.type != 'MESH': show_message("The active object is not a mesh.") return {'CANCELLED'} @@ -62,12 +47,13 @@ def find_nearest_vertex_group(obj, vertex): nearest_vertex = v return nearest_vertex - def CP77GroupUngroupedVerts(self, context): C = bpy.context obj = C.object current_mode = C.mode - + if not obj: + show_message("No active object. Please Select a Mesh and try again") + return {'CANCELLED'} if obj.type != 'MESH': show_message("The active object is not a mesh.") return {'CANCELLED'} @@ -100,7 +86,6 @@ def CP77GroupUngroupedVerts(self, context): print(e) return {'FINISHED'} - def trans_weights(self, context): current_mode = context.mode props = context.scene.cp77_panel_props From 6f064548513da557ecccc4a76089c65ad1678f09 Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:53:41 -0400 Subject: [PATCH 24/26] add shapekey functions --- i_scene_cp77_gltf/main/bartmoss_functions.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index f18ef21..897a894 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -61,10 +61,17 @@ def hasShapeKeys(obj): return False # Return the name of the shape key data block if the object has shape keys. -def getShapeKeyName(obj): +def getShapeKeyName(name): if hasShapeKeys(obj): - return obj.data.shape_keys.name + return obj.data.shape_keys[name] return "" + +def getShapeKeyByName(obj, name): + if obj.data.shape_keys: + for key_block in obj.data.shape_keys.key_blocks: + if key_block.name == name: + return key_block + return None # returns a dictionary with all the property names for the objects shape keys. def getShapeKeyProps(obj): From e798c5208d636cf6b4d0fd4a30bca1f5f15fde0c Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 03:16:44 -0400 Subject: [PATCH 25/26] more shapekey funcs --- i_scene_cp77_gltf/main/bartmoss_functions.py | 28 +++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index 897a894..2009ea3 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -56,23 +56,33 @@ def calculate_mesh_volume(obj): ## Returns True if the given object has shape keys, works for meshes and curves def hasShapeKeys(obj): if obj.id_data.type in ['MESH', 'CURVE']: - return True if obj.data.shape_keys else False - else: - return False - -# Return the name of the shape key data block if the object has shape keys. -def getShapeKeyName(name): + return obj.data.shape_keys != None + +def getShapeKeyNames(obj): if hasShapeKeys(obj): - return obj.data.shape_keys[name] + key_names = [] + for key_block in obj.data.shape_keys.key_blocks: + key_names.append(key_block.name) + return key_names return "" - + +# Return the name of the shape key data block if the object has shape keys. def getShapeKeyByName(obj, name): - if obj.data.shape_keys: + if hasShapeKeys(obj): for key_block in obj.data.shape_keys.key_blocks: if key_block.name == name: return key_block return None +def setActiveShapeKey(obj, name): + shape_key = getShapeKeyByName(obj, name) + if shape_key: + for index, key_block in enumerate(obj.data.shape_keys.key_blocks): + if key_block == shape_key: + obj.active_shape_key_index = index + return True + return False + # returns a dictionary with all the property names for the objects shape keys. def getShapeKeyProps(obj): From 19371703d705406c212bad257ecc2bbec8848aaf Mon Sep 17 00:00:00 2001 From: DoctorPresto <111536029+DoctorPresto@users.noreply.github.com> Date: Mon, 22 Jul 2024 04:42:54 -0400 Subject: [PATCH 26/26] automatically apply refitter, preserving garment support closes #118 --- i_scene_cp77_gltf/main/bartmoss_functions.py | 10 +++- i_scene_cp77_gltf/meshtools/meshtools.py | 49 ++++++++++++++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/i_scene_cp77_gltf/main/bartmoss_functions.py b/i_scene_cp77_gltf/main/bartmoss_functions.py index 2009ea3..75d7832 100644 --- a/i_scene_cp77_gltf/main/bartmoss_functions.py +++ b/i_scene_cp77_gltf/main/bartmoss_functions.py @@ -80,7 +80,7 @@ def setActiveShapeKey(obj, name): for index, key_block in enumerate(obj.data.shape_keys.key_blocks): if key_block == shape_key: obj.active_shape_key_index = index - return True + return shape_key return False # returns a dictionary with all the property names for the objects shape keys. @@ -106,12 +106,18 @@ def getCustomProps(obj): return props # returns a list of modifiers for the given object -def getMods(obj): +def getModNames(obj): mods = [] for mod in obj.modifiers: mods.append(mod.name) return mods +def getModByName(obj, name): + for mod in obj.modifiers: + if mod.name == name: + return mod + return None + # returns a list with the modifier properties of the given modifier. def getModProps(modifier): props = [] diff --git a/i_scene_cp77_gltf/meshtools/meshtools.py b/i_scene_cp77_gltf/meshtools/meshtools.py index 0467d4a..a8d2f5f 100644 --- a/i_scene_cp77_gltf/meshtools/meshtools.py +++ b/i_scene_cp77_gltf/meshtools/meshtools.py @@ -5,6 +5,7 @@ from .verttools import * from ..cyber_props import * from ..main.common import show_message +from ..main.bartmoss_functions import setActiveShapeKey, getShapeKeyNames, getModNames def CP77SubPrep(self, context, smooth_factor, merge_distance): scn = context.scene @@ -195,6 +196,47 @@ def CP77RefitChecker(self, context): print('refitter result:', refitter) return refitter +def applyModifierAsShapeKey(obj): + names = getModNames(obj) + print(names) + refitter = None + for name in names: + if 'AutoFitter' in name: + refitter = name + if refitter: + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + + bpy.ops.object.modifier_apply_as_shapekey(keep_modifier=False, modifier=refitter) + print(f"Applied modifier '{name}' as shape key.") + +def applyRefitter(obj): + applyModifierAsShapeKey(obj) + orignames = getShapeKeyNames(obj) + for name in orignames: + if 'AutoFitter' in name: + refitkey = setActiveShapeKey(obj, name) + refitkey.value = 1 + if 'Garment' in name: + gskey = setActiveShapeKey(obj, name) + gskey.value = 1 + + bpy.ops.object.shape_key_add(from_mix=True) + + gskey.value = 0 + gskey = setActiveShapeKey(obj, name) + bpy.ops.object.shape_key_remove(all=False) + newnames = getShapeKeyNames(obj) + setActiveShapeKey(obj, 'Basis') + bpy.ops.object.shape_key_remove(all=False) + for name in newnames: + if 'AutoFitter' in name: + refitkey = setActiveShapeKey(obj, name) + refitkey.name = 'Basis' + if name not in orignames: + newgs = setActiveShapeKey(obj, name) + newgs.name = 'GarmentSupport' + def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): selected_meshes = [obj for obj in context.selected_objects if obj.type == 'MESH'] scene = context.scene @@ -232,6 +274,7 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): print('refitting:', mesh.name, 'to:', target_body_name) lattice_modifier = mesh.modifiers.new(refitter_obj.name, 'LATTICE') lattice_modifier.object = refitter_obj + applyRefitter(mesh) return{'FINISHED'} @@ -296,6 +339,6 @@ def CP77Refit(context, refitter, target_body_path, target_body_name, fbx_rot): for mesh in selected_meshes: lattice_modifier = mesh.modifiers.new(new_lattice.name,'LATTICE') - for mesh in selected_meshes: - print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) - lattice_modifier.object = new_lattice + print('refitting:', mesh.name, 'to:', new_lattice["refitter_type"]) + lattice_modifier.object = new_lattice + applyRefitter(mesh)