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 0000000..0bf48ba Binary files /dev/null and b/wheels/jsmin-3.0.1-py3-none-any.whl differ