From c233e8aace9261029661cc0fe6f95b61314694ae Mon Sep 17 00:00:00 2001 From: Yusarina Date: Mon, 16 Dec 2024 13:57:12 +0000 Subject: [PATCH] Fixes --- blender_manifest.toml | 7 +- core/resonite_loader/common.py | 6 +- core/resonite_utils.py | 7 +- functions/uv_tools.py | 299 ---------------------------- wheels/jsmin-3.0.1-py3-none-any.whl | Bin 0 -> 13765 bytes 5 files changed, 10 insertions(+), 309 deletions(-) delete mode 100644 functions/uv_tools.py create mode 100644 wheels/jsmin-3.0.1-py3-none-any.whl diff --git a/blender_manifest.toml b/blender_manifest.toml index ac179c2..4a45665 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -16,7 +16,8 @@ license = [ ] wheels = [ - "lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", - "lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - "lz4-4.3.3-cp311-cp311-win_amd64.whl" + "./wheels/lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", + "./wheels/lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "./wheels/lz4-4.3.3-cp311-cp311-win_amd64.whl" ] + diff --git a/core/resonite_loader/common.py b/core/resonite_loader/common.py index 49ad0d9..69a3577 100644 --- a/core/resonite_loader/common.py +++ b/core/resonite_loader/common.py @@ -2,15 +2,13 @@ import ctypes import typing import struct from io import BytesIO +from typing import Any -def writeNullable(data: BytesIO, value: = None): - +def writeNullable(data: BytesIO, value: Any = None): data.write(struct.pack("?", value == None)) if(value == None): return data.write() - - def ReadCSharp_str(data: BytesIO) -> str: charamount = read7bitEncoded_int(data) diff --git a/core/resonite_utils.py b/core/resonite_utils.py index fd9d7df..f6938d2 100644 --- a/core/resonite_utils.py +++ b/core/resonite_utils.py @@ -2,12 +2,13 @@ from types import FrameType import bpy import bpy_extras from numpy import double +from typing import Set, Dict -from .common import get_active_armature, simplify_bonename, validate_armature -from bpy.types import Object, ShapeKey, Mesh, Context, Operator -from functools import lru_cache +from .common import get_active_armature, simplify_bonename, validate_armature, ProgressTracker +from bpy.types import Context, Operator from ..core.translations import t from ..core.dictionaries import bone_names, resonite_translations +from ..core.logging_setup import logger import re from .resonite_loader import resonite_animx, resonite_types diff --git a/functions/uv_tools.py b/functions/uv_tools.py deleted file mode 100644 index 66e6aae..0000000 --- a/functions/uv_tools.py +++ /dev/null @@ -1,299 +0,0 @@ -from typing import TypedDict -import bpy -from bpy.types import Operator, Object, Context, Mesh, MeshUVLoopLayer -import bmesh -import numpy as np -import math -from ..functions.translations import t -from ..core.register import register_wrap - -class GenerateLoopTreeResult(TypedDict): - tree: dict[str, set[str]] - selected_loops: dict[str,list[int]] - selected_verts: dict[str,int] - -@register_wrap -class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator): - bl_idname = "avatar_toolkit.align_uv_edges_to_target" - bl_label = t("avatar_toolkit.align_uv_edges_to_target.label") - bl_description = t("avatar_toolkit.align_uv_edges_to_target.desc") - bl_options = {'REGISTER', 'UNDO'} - - - - #all selected objects need to be meshes for this to work - @989onan - @classmethod - def poll(cls, context: Context): - if not ((context.view_layer.objects.active is not None) and (len(context.view_layer.objects.selected) > 0)): - return False - if context.mode != "EDIT_MESH": - return False - for obj in context.view_layer.objects.selected: - if obj.type != "MESH": - return False - if not context.space_data: - return False - if not context.space_data.show_uvedit: - return False - if context.scene.tool_settings.use_uv_select_sync: - return False - return True - - def execute(self, context: Context): - - - target: str = context.view_layer.objects.active.name #The object which we want to align every other selected object's selected UV vertex line to - - sources: list[str] = [i.name for i in context.view_layer.objects.selected] #The objects which we want to align their selected UV lines to the target's UV line - - prev_mode: str = bpy.context.object.mode - bpy.ops.object.mode_set(mode='OBJECT') - - - def generate_loop_tree(obj_name: str) -> GenerateLoopTreeResult: - print("Finding selected line for: \""+obj_name+"\"!") - - - vert_target_loops: dict[str,list[int]] = {} - vert_target_verts: dict[str,int] = {} - - me: Mesh = bpy.data.objects[obj_name].data - uv_lay: MeshUVLoopLayer = me.uv_layers.active - bm: bmesh.types.BMesh = bmesh.new() - bm.from_mesh(me) - bm.verts.ensure_lookup_table() - - - - # To explain: - # So loops in UV maps are X polygons that make up a face (So a MeshLoop represent a face and each vertex on that face is in order) - # - # For some preknowledge: - # When a mesh is UV unwrapped, if a vertice is shared by two different faces on the model in the viewport and the vertice of both faces are in - # the same position on the UV map, then it considers it one point and the user can move it - # (is why the uv map doesn't split apart when you try to move a vertex because that would be annoying) - # - # The problem: - # The problem is that the data for whether the uv corners of two faces that share a vertex physically being connected and selected as one vertex on the uv map does not exist - # Though thankfully, blender forcibly (whether you like it or not) merges vertices of a uv map if the vertex of two different faces are actually shared in the UI, - # allowing for the moving of vertices of 4 faces connected by a single vertex. Behavior every normal blender user is familiar with. - # - # The solution - # We can use this to our advantage, by finding vertices on the uv map that share the same coridinate as another vertex that is also selected. - # that way we can group each pair shared in a line as the same vertex, and identify the line using these pairs and using the data that says for certain - # that two vertices share the same face loop, and therefore are connected. - - #hmmm real stupid grimlin hours with this one. Using a string as the index of a dictionary of loop corners that end up on the same coordinate - - for k,i in enumerate(uv_lay.vertex_selection): #go through the selected vertices on object. - if (i.value == True) and (bm.verts[me.loops[k].vertex_index].select == True) and (bm.verts[me.loops[k].vertex_index].hide == False): #filter out vertices that are hidden from UV port - key = np.array(uv_lay.uv[k].vector[:]) - key = key.round(decimals=5) #make a key that is the position of a selected vertex - - if str(key) not in vert_target_loops: - vert_target_loops[str(key)] = [] #if the vertex's position is not a list yet, add it. - vert_target_loops[str(key)].append(k) #Basically, group vertices based on their position on a UV map as a list. - vert_target_verts[str(key)] = me.loops[k].vertex_index #associate the index of the physical vertex in real space with the coordinate of the uv vertices that share a position (Basically associate UV vert with real vert) - if len(vert_target_loops) > 4000: #This usually indicates that the user has a bunch of crap selected. - self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.too_much")) - return - print("Finding connections on line for \""+obj_name+"\"!") - me.validate() - - bm = bmesh.new() - bm.from_mesh(me) - - - #print(vert_target_loops) - #print(vert_target_verts) - tree: dict[str, set[str]] = {} - selected_verts = np.hstack(list(vert_target_loops.values())) - #print(selected_verts) - bm.verts.ensure_lookup_table() - for uvcoordsstr in vert_target_loops: - - uv_lay = me.uv_layers.active - - - #before this section, each vert_target_loops is just groupings of vertices that share coordinates. - # Using the data that determines UV face corners (uvloops) that are associated with the real vertex, - # and the uv face corners (loops) that are on the same faces as the vertices that share coordinates in - # vert_target_loops, we can now identify them - #TL;DR: pairs of vertices that share cooridinates (chain links) find their buddies (make chain connected) - - # Someone explain this better than me if you can please - @989onan - extension_loops = [] - loops = bm.verts[vert_target_verts[uvcoordsstr]].link_loops - loops_indexes = [i.index for i in loops] - for loop in vert_target_loops[uvcoordsstr]: - if loop in loops_indexes: - loop_obj = loops[loops_indexes.index(loop)] - extension_loops.append(loop_obj.link_loop_next.index) - extension_loops.append(loop_obj.link_loop_prev.index) - - - - - - #make a tree out of the vertices we identified as sharing faces with the vertices in vert_target_loops, and then link them together in a dictionary. - #the order of this dictionary is unknown. - # Someone explain this better than me if you can please - @989onan - tree[uvcoordsstr] = set() - - for i in extension_loops: - if i in selected_verts: - key = np.array(uv_lay.uv[i].vector[:]) - key = key.round(decimals=5) - tree[uvcoordsstr].add(str(key)) - - if uvcoordsstr in tree: - if len(tree[uvcoordsstr]) > 2: - self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.need_a_line").format(obj=obj_name)) - return {'FINISHED'} - - uv_lay = me.uv_layers.active - for uvcoordstr in vert_target_loops: - for loop in vert_target_loops[uvcoordstr]: - uv_lay.vertex_selection[loop].value = True - - - bm.free() - me.validate() - print("found UV line connections for \""+obj_name+"\":") - #print(tree) - - return {"tree":tree,"selected_loops":vert_target_loops,"selected_verts":vert_target_verts} - - - - #This function uses the previous point to find the next point based on connected loops and faces. - def sort_uv_tree(originaltree: dict[str, set[str]], obj_name: str): - sortedtree: dict[str, set[str]] = originaltree.copy() - startpoints: list[str] = [] - for i in sortedtree: - if len(sortedtree[i]) < 2: - startpoints.append(i) - - if len(startpoints) != 2: - self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.need_a_line").format(obj=obj_name)) - return - - a_list1 = startpoints[0].replace(", "," ").replace("[","").replace("]","").split() - map_object1 = map(float, a_list1) - uvcoords1 = list(map_object1) - a_list2 = startpoints[1].replace(", "," ").replace("[","").replace("]","").split() - map_object2 = map(float, a_list2) - uvcoords2 = list(map_object2) - - cursor = context.space_data.cursor_location - - startpoint = None - if math.sqrt( (((uvcoords1[0]) - (cursor[0])) **2) + (((uvcoords1[1]) - (cursor[1])) **2) ) > math.sqrt( (((uvcoords2[0]) - (cursor[0])) **2) + (((uvcoords2[1]) - (cursor[1])) **2) ): - startpoint = startpoints[0] - else: - startpoint = startpoints[1] - - #Wew my first actual recursive sort! - @989onan - def recursive_sort_uv_tree(point: str, sortedfinal: list[str]): - #print("appending "+point) - sortedfinal.append(point) - - new_point: str = "" - for i in sortedtree: - if point in sortedtree[i]: - new_point = i - removed_value = sortedtree.pop(i) - #print(removed_value) - break - - if new_point == "": - print("BROKE OUT OF SORTING, FINAL TREE (Should be empty, if not you errored here!):") - print(sortedtree) - - return sortedfinal - - return recursive_sort_uv_tree(new_point, sortedfinal) - - array = [] - - sortedtree.pop(startpoint) - return recursive_sort_uv_tree(startpoint, array) - - def lerp(v0, v1, t): - return v0 + t * (v1 - v0) - - - target_data: GenerateLoopTreeResult = generate_loop_tree(target) - sorted_target_tree = sort_uv_tree(target_data["tree"], target) - print("sorted target.") - #print(sorted_target_tree) - - for source in sources: - if source == target: - continue - - #create our list of points that is a chain. then sort the chain into the correct order based on connections of vertices and the faces that the vertices make up in the UV map. - try: - source_data = generate_loop_tree(source) - sorted_source_tree = sort_uv_tree(source_data["tree"], source) - print("Sorted source "+source) - print(sorted_source_tree) - - vertex_factor = float(len(sorted_target_tree)-1) / (float(len(sorted_source_tree)-1)) - - print(str(vertex_factor)+" = "+str(float(len(sorted_target_tree)-1)) + " / " + str((float(len(sorted_source_tree)-1)))+")") - except Exception as e: - print(e) - return {'FINISHED'} - - for k,i in enumerate(sorted_source_tree): - - try: - #find where we are on the target edges, to interpolate the current point we're placing along the target point's line. - progress_along_edge = (float(k)*vertex_factor) - previous_vertex_index = math.floor(progress_along_edge) - next_vertex_index = math.ceil(progress_along_edge) - - - #find the uv coordinates of the previous and next points on the target uv line. - a_list1 = sorted_target_tree[previous_vertex_index].replace(", "," ").replace("[","").replace("]","").split() - map_object1 = map(float, a_list1) - previous_point = list(map_object1) - a_list2 = sorted_target_tree[next_vertex_index].replace(", "," ").replace("[","").replace("]","").split() - map_object2 = map(float, a_list2) - next_point = list(map_object2) - - - - #create a point between these two values that represents a decimal 0-1 going where we are to where we are going between the two current points on the edge we are targeting this whole shebang with. - progress_between_points = progress_along_edge - int(progress_along_edge) - lerped_point = [lerp(previous_point[0],next_point[0],progress_between_points),lerp(previous_point[1],next_point[1],progress_between_points)] - - #grab our uv face corners for each uv coord that we saved. - #Since each face is considered separate internally, we have to treat each connected face to a vertex in a uv map as separate entities/vertexes. - #basically pretend they are split apart. - uv_face_corners = source_data["selected_loops"][i] - #print("doing from vertex "+str(previous_vertex_index)+" to "+str(next_vertex_index)+" total progress: "+str(progress_along_edge)) - - - - me: Mesh = bpy.data.objects[source].data - me.validate() - bm: bmesh.types.BMesh = bmesh.new() - bm.from_mesh(me) - uv_lay: MeshUVLoopLayer = me.uv_layers.active - bm.verts.ensure_lookup_table() - for corner in uv_face_corners: - uv_lay.uv[corner].vector = lerped_point #put the vertcies at the point we calculated. - except: - print("This is probably fine? - @989onan") #TODO: What happened here? The magic of making code so complex you forget if this is even an issue. - @989onan - #Note: The above print statement may require reading what this entire operator does before understanding if the above print statment means anything - #so if you value your time, just do a bunch of tests until the above print stament is ran or something - #if in doubt and you cannot get it to run, just spend some time trying to understand the operator - @989onan - - print("Finished mesh \""+source+"\" for UV's") - - - - bpy.ops.object.mode_set(mode=prev_mode) - return {'FINISHED'} \ No newline at end of file diff --git a/wheels/jsmin-3.0.1-py3-none-any.whl b/wheels/jsmin-3.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..0bf48bab4fedf761d9938064838a64cc1a0f209b GIT binary patch literal 13765 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 W*ng}Rf46Hv0yzF=yBqU=T>XFJHrWFJ literal 0 HcmV?d00001