diff --git a/editor/body_mesh/N00_000_00_FaceEyelash_00_FACE (Instance).json b/editor/body_mesh/N00_000_00_FaceEyelash_00_FACE (Instance).json new file mode 100644 index 0000000..89ec830 --- /dev/null +++ b/editor/body_mesh/N00_000_00_FaceEyelash_00_FACE (Instance).json @@ -0,0 +1,118 @@ +{ + "description": "Edge UV data for N00_000_00_FaceEyelash_00_FACE (Instance)", + "total_edges": 112, + "edge_uvs": [ + [[0.544165, 0.631242], [0.505881, 0.532443]], + [[0.630136, 0.631242], [0.677712, 0.736516]], + [[0.725608, 0.736516], [0.771321, 0.631242]], + [[0.853103, 0.736516], [0.878623, 0.624128]], + [[0.964185, 0.530252], [0.995744, 0.41732]], + [[0.455835, 0.631242], [0.494119, 0.532443]], + [[0.415292, 0.736516], [0.455835, 0.631242]], + [[0.369864, 0.736516], [0.322288, 0.855226]], + [[0.274392, 0.855226], [0.228679, 0.736516]], + [[0.228679, 0.983897], [0.183674, 0.855226]], + [[0.108634, 0.726003], [0.098715, 0.59326]], + [[0.064526, 0.469594], [0.089458, 0.517009]], + [[0.004256, 0.243915], [0.035177, 0.384901]], + [[0.584708, 0.855226], [0.630136, 0.983897]], + [[0.544165, 0.736516], [0.584708, 0.855226]], + [[0.630136, 0.532794], [0.677712, 0.631242]], + [[0.771321, 0.631242], [0.810676, 0.532573]], + [[0.816326, 0.855226], [0.853103, 0.736516]], + [[0.878623, 0.624128], [0.886484, 0.513762]], + [[0.913097, 0.33099], [0.936432, 0.243915]], + [[0.455835, 0.855226], [0.494119, 0.736516]], + [[0.415292, 0.983897], [0.455835, 0.855226]], + [[0.322288, 0.855226], [0.274392, 0.983897]], + [[0.228679, 0.736516], [0.188503, 0.631242]], + [[0.137685, 0.855226], [0.108634, 0.726003]], + [[0.067401, 0.59014], [0.098715, 0.59326]], + [[0.087542, 0.421032], [0.107709, 0.454409]], + [[0.584708, 0.631242], [0.544165, 0.532493]], + [[0.677712, 0.736516], [0.725608, 0.855226]], + [[0.820334, 0.983897], [0.862315, 0.855226]], + [[0.920375, 0.68592], [0.962269, 0.684334]], + [[0.886484, 0.513762], [0.910542, 0.517009]], + [[0.912458, 0.421032], [0.936432, 0.355213]], + [[0.415292, 0.631242], [0.455835, 0.532493]], + [[0.274392, 0.532794], [0.228679, 0.631242]], + [[0.274392, 0.983897], [0.228679, 0.855226]], + [[0.188503, 0.631242], [0.157148, 0.532738]], + [[0.126838, 0.983897], [0.089528, 0.849711]], + [[0.004256, 0.78906], [0.051958, 0.804902]], + [[0.035815, 0.530252], [0.067401, 0.59014]], + [[0.035177, 0.243915], [0.063568, 0.355213]], + [[0.544165, 0.736516], [0.505881, 0.631242]], + [[0.584708, 0.631242], [0.630136, 0.736516]], + [[0.677712, 0.631242], [0.725608, 0.736516]], + [[0.813115, 0.736516], [0.84716, 0.631242]], + [[0.910472, 0.849711], [0.920375, 0.68592]], + [[0.962269, 0.684334], [0.995744, 0.596958]], + [[0.892291, 0.378519], [0.913097, 0.33099]], + [[0.892291, 0.3095], [0.913097, 0.243915]], + [[0.415292, 0.736516], [0.369864, 0.855226]], + [[0.369864, 0.631242], [0.322288, 0.736516]], + [[0.274392, 0.736516], [0.228679, 0.631242]], + [[0.146897, 0.736516], [0.121376, 0.624128]], + [[0.004256, 0.41732], [0.035815, 0.530252]], + [[0.505881, 0.855226], [0.544165, 0.983897]], + [[0.584708, 0.532593], [0.630136, 0.631242]], + [[0.677712, 0.53292], [0.725608, 0.631242]], + [[0.771321, 0.855226], [0.813115, 0.736516]], + [[0.84716, 0.631242], [0.869062, 0.53317]], + [[0.93276, 0.983897], [0.948042, 0.804902]], + [[0.935474, 0.469594], [0.964823, 0.384901]], + [[0.455835, 0.736516], [0.494119, 0.631242]], + [[0.415292, 0.855226], [0.455835, 0.736516]], + [[0.369864, 0.855226], [0.322288, 0.983897]], + [[0.369864, 0.532794], [0.322288, 0.631242]], + [[0.228679, 0.631242], [0.189324, 0.532573]], + [[0.183674, 0.855226], [0.146897, 0.736516]], + [[0.121376, 0.624128], [0.113516, 0.513762]], + [[0.063568, 0.243915], [0.086903, 0.33099]], + [[0.630136, 0.855226], [0.677712, 0.983897]], + [[0.584708, 0.736516], [0.630136, 0.855226]], + [[0.677712, 0.855226], [0.725608, 0.983897]], + [[0.630136, 0.736516], [0.677712, 0.855226]], + [[0.725608, 0.855226], [0.771321, 0.736516]], + [[0.771321, 0.983897], [0.816326, 0.855226]], + [[0.891366, 0.726003], [0.901285, 0.59326]], + [[0.910542, 0.517009], [0.935474, 0.469594]], + [[0.964823, 0.384901], [0.995744, 0.243915]], + [[0.322288, 0.736516], [0.274392, 0.855226]], + [[0.179666, 0.983897], [0.137685, 0.855226]], + [[0.037731, 0.684334], [0.079625, 0.68592]], + [[0.089458, 0.517009], [0.113516, 0.513762]], + [[0.063568, 0.355213], [0.087542, 0.421032]], + [[0.544165, 0.855226], [0.584708, 0.983897]], + [[0.505881, 0.736516], [0.544165, 0.855226]], + [[0.725608, 0.983897], [0.771321, 0.855226]], + [[0.771321, 0.736516], [0.811497, 0.631242]], + [[0.862315, 0.855226], [0.891366, 0.726003]], + [[0.901285, 0.59326], [0.932599, 0.59014]], + [[0.892291, 0.454409], [0.912458, 0.421032]], + [[0.455835, 0.983897], [0.494119, 0.855226]], + [[0.415292, 0.855226], [0.369864, 0.983897]], + [[0.415292, 0.631242], [0.369864, 0.736516]], + [[0.322288, 0.631242], [0.274392, 0.736516]], + [[0.186885, 0.736516], [0.15284, 0.631242]], + [[0.089528, 0.849711], [0.079625, 0.68592]], + [[0.004256, 0.596958], [0.037731, 0.684334]], + [[0.086903, 0.33099], [0.107709, 0.378519]], + [[0.086903, 0.243915], [0.107709, 0.3095]], + [[0.584708, 0.736516], [0.544165, 0.631242]], + [[0.725608, 0.532794], [0.771321, 0.631242]], + [[0.811497, 0.631242], [0.842852, 0.532738]], + [[0.873162, 0.983897], [0.910472, 0.849711]], + [[0.948042, 0.804902], [0.995744, 0.78906]], + [[0.932599, 0.59014], [0.964185, 0.530252]], + [[0.936432, 0.355213], [0.964823, 0.243915]], + [[0.415292, 0.532593], [0.369864, 0.631242]], + [[0.322288, 0.53292], [0.274392, 0.631242]], + [[0.228679, 0.855226], [0.186885, 0.736516]], + [[0.15284, 0.631242], [0.130938, 0.53317]], + [[0.06724, 0.983897], [0.051958, 0.804902]], + [[0.035177, 0.384901], [0.064526, 0.469594]] + ] +} diff --git a/editor/body_mesh/N00_000_00_HairBack_00_HAIR (Instance).json b/editor/body_mesh/N00_000_00_HairBack_00_HAIR (Instance).json new file mode 100644 index 0000000..d3e1b02 --- /dev/null +++ b/editor/body_mesh/N00_000_00_HairBack_00_HAIR (Instance).json @@ -0,0 +1,8006 @@ +{ + "description": "Edge UV data for N00_000_00_HairBack_00_HAIR (Instance)", + "total_edges": 800, + "edge_uvs": [ + [ + [ + 0.233158, + 0.117595 + ], + [ + 0.25216, + 0.103709 + ] + ], + [ + [ + 0.314963, + 0.115737 + ], + [ + 0.293323, + 0.102491 + ] + ], + [ + [ + 0.21537, + 0.22932 + ], + [ + 0.238106, + 0.200631 + ] + ], + [ + [ + 0.238106, + 0.200631 + ], + [ + 0.260082, + 0.171847 + ] + ], + [ + [ + 0.209637, + 0.255673 + ], + [ + 0.234328, + 0.226029 + ] + ], + [ + [ + 0.234328, + 0.226029 + ], + [ + 0.256826, + 0.197208 + ] + ], + [ + [ + 0.300177, + 0.240364 + ], + [ + 0.330308, + 0.208874 + ] + ], + [ + [ + 0.467661, + 0.115348 + ], + [ + 0.437613, + 0.09577 + ] + ], + [ + [ + 0.466577, + 0.146896 + ], + [ + 0.434533, + 0.131938 + ] + ], + [ + [ + 0.500062, + 0.096383 + ], + [ + 0.468755, + 0.075575 + ] + ], + [ + [ + 0.500062, + 0.146787 + ], + [ + 0.53292, + 0.132105 + ] + ], + [ + [ + 0.5, + 0.160438 + ], + [ + 0.533423, + 0.146896 + ] + ], + [ + [ + 0.597287, + 0.131662 + ], + [ + 0.624205, + 0.114187 + ] + ], + [ + [ + 0.56758, + 0.160566 + ], + [ + 0.599774, + 0.147417 + ] + ], + [ + [ + 0.688918, + 0.13133 + ], + [ + 0.709648, + 0.117067 + ] + ], + [ + [ + 0.300681, + 0.271195 + ], + [ + 0.330526, + 0.235727 + ] + ], + [ + [ + 0.363605, + 0.261117 + ], + [ + 0.396321, + 0.287274 + ] + ], + [ + [ + 0.282836, + 0.375678 + ], + [ + 0.304248, + 0.335601 + ] + ], + [ + [ + 0.255936, + 0.458173 + ], + [ + 0.267078, + 0.417156 + ] + ], + [ + [ + 0.21579, + 0.538767 + ], + [ + 0.216688, + 0.494834 + ] + ], + [ + [ + 0.088675, + 0.5267 + ], + [ + 0.098665, + 0.559052 + ] + ], + [ + [ + 0.098665, + 0.559052 + ], + [ + 0.10679, + 0.593635 + ] + ], + [ + [ + 0.07422, + 0.58772 + ], + [ + 0.089314, + 0.586671 + ] + ], + [ + [ + 0.082729, + 0.603469 + ], + [ + 0.10257, + 0.607055 + ] + ], + [ + [ + 0.022262, + 0.538836 + ], + [ + 0.02804, + 0.57075 + ] + ], + [ + [ + 0.139071, + 0.611292 + ], + [ + 0.163659, + 0.598859 + ] + ], + [ + [ + 0.182929, + 0.618378 + ], + [ + 0.211587, + 0.611361 + ] + ], + [ + [ + 0.218374, + 0.799674 + ], + [ + 0.210021, + 0.783289 + ] + ], + [ + [ + 0.210021, + 0.783289 + ], + [ + 0.226896, + 0.783692 + ] + ], + [ + [ + 0.230509, + 0.766143 + ], + [ + 0.211823, + 0.731302 + ] + ], + [ + [ + 0.258947, + 0.758701 + ], + [ + 0.237674, + 0.722897 + ] + ], + [ + [ + 0.5, + 0.928052 + ], + [ + 0.459105, + 0.912773 + ] + ], + [ + [ + 0.459105, + 0.912773 + ], + [ + 0.419788, + 0.889358 + ] + ], + [ + [ + 0.25201, + 0.650015 + ], + [ + 0.223542, + 0.657377 + ] + ], + [ + [ + 0.284003, + 0.581871 + ], + [ + 0.251487, + 0.580464 + ] + ], + [ + [ + 0.278948, + 0.543894 + ], + [ + 0.280494, + 0.506683 + ] + ], + [ + [ + 0.308188, + 0.366345 + ], + [ + 0.333535, + 0.32811 + ] + ], + [ + [ + 0.341975, + 0.386595 + ], + [ + 0.308188, + 0.366345 + ] + ], + [ + [ + 0.371255, + 0.379294 + ], + [ + 0.336453, + 0.358014 + ] + ], + [ + [ + 0.356345, + 0.439287 + ], + [ + 0.376922, + 0.406445 + ] + ], + [ + [ + 0.278948, + 0.543894 + ], + [ + 0.311648, + 0.549111 + ] + ], + [ + [ + 0.317674, + 0.669164 + ], + [ + 0.355194, + 0.662831 + ] + ], + [ + [ + 0.355194, + 0.662831 + ], + [ + 0.39101, + 0.652901 + ] + ], + [ + [ + 0.423146, + 0.869976 + ], + [ + 0.38911, + 0.835102 + ] + ], + [ + [ + 0.540895, + 0.912773 + ], + [ + 0.580212, + 0.889358 + ] + ], + [ + [ + 0.739889, + 0.836569 + ], + [ + 0.756892, + 0.808607 + ] + ], + [ + [ + 0.809906, + 0.73861 + ], + [ + 0.788177, + 0.731302 + ] + ], + [ + [ + 0.87394, + 0.661515 + ], + [ + 0.851589, + 0.654493 + ] + ], + [ + [ + 0.851589, + 0.654493 + ], + [ + 0.824398, + 0.64616 + ] + ], + [ + [ + 0.807253, + 0.692467 + ], + [ + 0.781497, + 0.683718 + ] + ], + [ + [ + 0.758671, + 0.699801 + ], + [ + 0.736606, + 0.738336 + ] + ], + [ + [ + 0.654419, + 0.838555 + ], + [ + 0.62177, + 0.874938 + ] + ], + [ + [ + 0.707875, + 0.755652 + ], + [ + 0.681978, + 0.796554 + ] + ], + [ + [ + 0.573384, + 0.847025 + ], + [ + 0.538207, + 0.87747 + ] + ], + [ + [ + 0.570657, + 0.821564 + ], + [ + 0.536529, + 0.853576 + ] + ], + [ + [ + 0.5, + 0.803042 + ], + [ + 0.464763, + 0.827433 + ] + ], + [ + [ + 0.602675, + 0.786112 + ], + [ + 0.566235, + 0.772121 + ] + ], + [ + [ + 0.563986, + 0.748378 + ], + [ + 0.530792, + 0.727973 + ] + ], + [ + [ + 0.444458, + 0.668171 + ], + [ + 0.412997, + 0.68793 + ] + ], + [ + [ + 0.472003, + 0.672063 + ], + [ + 0.447513, + 0.641045 + ] + ], + [ + [ + 0.426825, + 0.61159 + ], + [ + 0.407078, + 0.583879 + ] + ], + [ + [ + 0.355215, + 0.540977 + ], + [ + 0.326968, + 0.535028 + ] + ], + [ + [ + 0.40935, + 0.562043 + ], + [ + 0.389112, + 0.542238 + ] + ], + [ + [ + 0.408952, + 0.540655 + ], + [ + 0.385462, + 0.523682 + ] + ], + [ + [ + 0.385462, + 0.523682 + ], + [ + 0.399534, + 0.498459 + ] + ], + [ + [ + 0.393901, + 0.477574 + ], + [ + 0.413514, + 0.449947 + ] + ], + [ + [ + 0.435497, + 0.396171 + ], + [ + 0.466208, + 0.366365 + ] + ], + [ + [ + 0.5, + 0.365493 + ], + [ + 0.465289, + 0.33844 + ] + ], + [ + [ + 0.569836, + 0.227124 + ], + [ + 0.534601, + 0.199264 + ] + ], + [ + [ + 0.5, + 0.337617 + ], + [ + 0.53533, + 0.310525 + ] + ], + [ + [ + 0.5, + 0.365493 + ], + [ + 0.534711, + 0.33844 + ] + ], + [ + [ + 0.570434, + 0.28432 + ], + [ + 0.603755, + 0.25777 + ] + ], + [ + [ + 0.636813, + 0.204911 + ], + [ + 0.602622, + 0.178717 + ] + ], + [ + [ + 0.699319, + 0.271195 + ], + [ + 0.669474, + 0.235727 + ] + ], + [ + [ + 0.568794, + 0.340937 + ], + [ + 0.603172, + 0.316418 + ] + ], + [ + [ + 0.739918, + 0.171847 + ], + [ + 0.716415, + 0.148775 + ] + ], + [ + [ + 0.754186, + 0.149596 + ], + [ + 0.733387, + 0.132294 + ] + ], + [ + [ + 0.774078, + 0.284195 + ], + [ + 0.80603, + 0.319876 + ] + ], + [ + [ + 0.598425, + 0.37336 + ], + [ + 0.633207, + 0.350878 + ] + ], + [ + [ + 0.685441, + 0.395258 + ], + [ + 0.701499, + 0.432598 + ] + ], + [ + [ + 0.658025, + 0.386595 + ], + [ + 0.677435, + 0.422433 + ] + ], + [ + [ + 0.5, + 0.365493 + ], + [ + 0.532716, + 0.393467 + ] + ], + [ + [ + 0.5, + 0.392533 + ], + [ + 0.531623, + 0.418915 + ] + ], + [ + [ + 0.668587, + 0.447966 + ], + [ + 0.65102, + 0.413728 + ] + ], + [ + [ + 0.732471, + 0.483262 + ], + [ + 0.723584, + 0.44444 + ] + ], + [ + [ + 0.691583, + 0.457998 + ], + [ + 0.700785, + 0.493024 + ] + ], + [ + [ + 0.719506, + 0.506683 + ], + [ + 0.721052, + 0.543894 + ] + ], + [ + [ + 0.559572, + 0.445551 + ], + [ + 0.582242, + 0.47177 + ] + ], + [ + [ + 0.656892, + 0.522611 + ], + [ + 0.687639, + 0.514694 + ] + ], + [ + [ + 0.556536, + 0.467334 + ], + [ + 0.577761, + 0.493264 + ] + ], + [ + [ + 0.553158, + 0.489311 + ], + [ + 0.57303, + 0.516088 + ] + ], + [ + [ + 0.474963, + 0.512405 + ], + [ + 0.5, + 0.486199 + ] + ], + [ + [ + 0.5, + 0.486199 + ], + [ + 0.525038, + 0.512405 + ] + ], + [ + [ + 0.5, + 0.645097 + ], + [ + 0.475107, + 0.617408 + ] + ], + [ + [ + 0.568655, + 0.563245 + ], + [ + 0.591048, + 0.540655 + ] + ], + [ + [ + 0.570025, + 0.587166 + ], + [ + 0.59065, + 0.562043 + ] + ], + [ + [ + 0.627177, + 0.727407 + ], + [ + 0.591542, + 0.71415 + ] + ], + [ + [ + 0.769496, + 0.630467 + ], + [ + 0.739043, + 0.624847 + ] + ], + [ + [ + 0.691985, + 0.603525 + ], + [ + 0.655828, + 0.604279 + ] + ], + [ + [ + 0.637809, + 0.569945 + ], + [ + 0.622075, + 0.59842 + ] + ], + [ + [ + 0.630493, + 0.554684 + ], + [ + 0.616048, + 0.578959 + ] + ], + [ + [ + 0.644785, + 0.540977 + ], + [ + 0.624427, + 0.54265 + ] + ], + [ + [ + 0.89321, + 0.593635 + ], + [ + 0.901335, + 0.559052 + ] + ], + [ + [ + 0.912054, + 0.596559 + ], + [ + 0.89321, + 0.593635 + ] + ], + [ + [ + 0.933855, + 0.5884 + ], + [ + 0.930275, + 0.58033 + ] + ], + [ + [ + 0.318833, + 0.10062 + ], + [ + 0.29612, + 0.088017 + ] + ], + [ + [ + 0.220307, + 0.20386 + ], + [ + 0.242124, + 0.17378 + ] + ], + [ + [ + 0.242124, + 0.17378 + ], + [ + 0.263477, + 0.149231 + ] + ], + [ + [ + 0.332982, + 0.165653 + ], + [ + 0.367694, + 0.147781 + ] + ], + [ + [ + 0.363616, + 0.1818 + ], + [ + 0.398491, + 0.161839 + ] + ], + [ + [ + 0.436, + 0.11491 + ], + [ + 0.409502, + 0.095408 + ] + ], + [ + [ + 0.466257, + 0.16026 + ], + [ + 0.433309, + 0.147081 + ] + ], + [ + [ + 0.468215, + 0.096088 + ], + [ + 0.439301, + 0.075575 + ] + ], + [ + [ + 0.500062, + 0.096383 + ], + [ + 0.531245, + 0.075575 + ] + ], + [ + [ + 0.562387, + 0.09577 + ], + [ + 0.58657, + 0.075575 + ] + ], + [ + [ + 0.532339, + 0.115348 + ], + [ + 0.562387, + 0.09577 + ] + ], + [ + [ + 0.618943, + 0.095866 + ], + [ + 0.645326, + 0.081219 + ] + ], + [ + [ + 0.594159, + 0.114389 + ], + [ + 0.618943, + 0.095866 + ] + ], + [ + [ + 0.566691, + 0.147081 + ], + [ + 0.597287, + 0.131662 + ] + ], + [ + [ + 0.706677, + 0.102491 + ], + [ + 0.726509, + 0.088896 + ] + ], + [ + [ + 0.685037, + 0.115737 + ], + [ + 0.706677, + 0.102491 + ] + ], + [ + [ + 0.465928, + 0.176336 + ], + [ + 0.500122, + 0.199313 + ] + ], + [ + [ + 0.430877, + 0.199884 + ], + [ + 0.465928, + 0.176336 + ] + ], + [ + [ + 0.331127, + 0.265825 + ], + [ + 0.363348, + 0.231764 + ] + ], + [ + [ + 0.277946, + 0.343455 + ], + [ + 0.301993, + 0.30367 + ] + ], + [ + [ + 0.224581, + 0.449362 + ], + [ + 0.239687, + 0.398708 + ] + ], + [ + [ + 0.169476, + 0.527685 + ], + [ + 0.198921, + 0.518188 + ] + ], + [ + [ + 0.18476, + 0.544286 + ], + [ + 0.21579, + 0.538767 + ] + ], + [ + [ + 0.21579, + 0.538767 + ], + [ + 0.247589, + 0.539539 + ] + ], + [ + [ + 0.069725, + 0.58033 + ], + [ + 0.066145, + 0.5884 + ] + ], + [ + [ + 0.022262, + 0.538836 + ], + [ + 0.00496, + 0.539595 + ] + ], + [ + [ + 0.132145, + 0.636703 + ], + [ + 0.155475, + 0.626319 + ] + ], + [ + [ + 0.234256, + 0.744894 + ], + [ + 0.214779, + 0.708216 + ] + ], + [ + [ + 0.254229, + 0.779213 + ], + [ + 0.234256, + 0.744894 + ] + ], + [ + [ + 0.241329, + 0.699801 + ], + [ + 0.214779, + 0.708216 + ] + ], + [ + [ + 0.45829, + 0.925305 + ], + [ + 0.417398, + 0.90411 + ] + ], + [ + [ + 0.417398, + 0.90411 + ], + [ + 0.37823, + 0.874938 + ] + ], + [ + [ + 0.230504, + 0.630467 + ], + [ + 0.203541, + 0.639224 + ] + ], + [ + [ + 0.260958, + 0.624847 + ], + [ + 0.230504, + 0.630467 + ] + ], + [ + [ + 0.251487, + 0.580464 + ], + [ + 0.221643, + 0.584325 + ] + ], + [ + [ + 0.263258, + 0.522244 + ], + [ + 0.267529, + 0.483262 + ] + ], + [ + [ + 0.401575, + 0.37336 + ], + [ + 0.366793, + 0.350878 + ] + ], + [ + [ + 0.326968, + 0.535028 + ], + [ + 0.294957, + 0.52845 + ] + ], + [ + [ + 0.370709, + 0.618808 + ], + [ + 0.353512, + 0.586429 + ] + ], + [ + [ + 0.325935, + 0.645079 + ], + [ + 0.36295, + 0.640103 + ] + ], + [ + [ + 0.329765, + 0.755039 + ], + [ + 0.367964, + 0.750203 + ] + ], + [ + [ + 0.367964, + 0.750203 + ], + [ + 0.404337, + 0.739531 + ] + ], + [ + [ + 0.426616, + 0.847025 + ], + [ + 0.393557, + 0.810872 + ] + ], + [ + [ + 0.5, + 0.928052 + ], + [ + 0.540895, + 0.912773 + ] + ], + [ + [ + 0.54171, + 0.925305 + ], + [ + 0.582602, + 0.90411 + ] + ], + [ + [ + 0.667153, + 0.869627 + ], + [ + 0.69557, + 0.834032 + ] + ], + [ + [ + 0.797605, + 0.78162 + ], + [ + 0.79131, + 0.770216 + ] + ], + [ + [ + 0.745771, + 0.779213 + ], + [ + 0.725253, + 0.813019 + ] + ], + [ + [ + 0.90165, + 0.620475 + ], + [ + 0.882146, + 0.620779 + ] + ], + [ + [ + 0.882146, + 0.620779 + ], + [ + 0.860928, + 0.611292 + ] + ], + [ + [ + 0.732017, + 0.717423 + ], + [ + 0.707875, + 0.755652 + ] + ], + [ + [ + 0.463471, + 0.853576 + ], + [ + 0.429343, + 0.821564 + ] + ], + [ + [ + 0.844525, + 0.626319 + ], + [ + 0.817071, + 0.618378 + ] + ], + [ + [ + 0.431625, + 0.795845 + ], + [ + 0.393557, + 0.810872 + ] + ], + [ + [ + 0.466857, + 0.77763 + ], + [ + 0.431625, + 0.795845 + ] + ], + [ + [ + 0.5, + 0.755914 + ], + [ + 0.466857, + 0.77763 + ] + ], + [ + [ + 0.566235, + 0.772121 + ], + [ + 0.532056, + 0.753948 + ] + ], + [ + [ + 0.530792, + 0.727973 + ], + [ + 0.5, + 0.702146 + ] + ], + [ + [ + 0.561376, + 0.722601 + ], + [ + 0.529418, + 0.700434 + ] + ], + [ + [ + 0.383952, + 0.578959 + ], + [ + 0.369507, + 0.554684 + ] + ], + [ + [ + 0.431345, + 0.563245 + ], + [ + 0.408952, + 0.540655 + ] + ], + [ + [ + 0.399534, + 0.498459 + ], + [ + 0.417758, + 0.47177 + ] + ], + [ + [ + 0.437847, + 0.421761 + ], + [ + 0.467284, + 0.393467 + ] + ], + [ + [ + 0.5, + 0.28126 + ], + [ + 0.464484, + 0.254117 + ] + ], + [ + [ + 0.534711, + 0.33844 + ], + [ + 0.569956, + 0.312753 + ] + ], + [ + [ + 0.668873, + 0.265825 + ], + [ + 0.636652, + 0.231764 + ] + ], + [ + [ + 0.635956, + 0.291522 + ], + [ + 0.668873, + 0.265825 + ] + ], + [ + [ + 0.533792, + 0.366365 + ], + [ + 0.568794, + 0.340937 + ] + ], + [ + [ + 0.716415, + 0.148775 + ], + [ + 0.688918, + 0.13133 + ] + ], + [ + [ + 0.712966, + 0.131845 + ], + [ + 0.730648, + 0.118058 + ] + ], + [ + [ + 0.736523, + 0.149231 + ], + [ + 0.712966, + 0.131845 + ] + ], + [ + [ + 0.749326, + 0.118071 + ], + [ + 0.768179, + 0.131826 + ] + ], + [ + [ + 0.750214, + 0.281101 + ], + [ + 0.78091, + 0.32009 + ] + ], + [ + [ + 0.791913, + 0.360786 + ], + [ + 0.823802, + 0.3884 + ] + ], + [ + [ + 0.668873, + 0.265825 + ], + [ + 0.698007, + 0.30367 + ] + ], + [ + [ + 0.698007, + 0.30367 + ], + [ + 0.722054, + 0.343455 + ] + ], + [ + [ + 0.760313, + 0.398708 + ], + [ + 0.775419, + 0.449362 + ] + ], + [ + [ + 0.566847, + 0.368986 + ], + [ + 0.594431, + 0.400545 + ] + ], + [ + [ + 0.712819, + 0.469667 + ], + [ + 0.701499, + 0.432598 + ] + ], + [ + [ + 0.712819, + 0.469667 + ], + [ + 0.719506, + 0.506683 + ] + ], + [ + [ + 0.700785, + 0.493024 + ], + [ + 0.705042, + 0.52845 + ] + ], + [ + [ + 0.867298, + 0.574218 + ], + [ + 0.873927, + 0.537991 + ] + ], + [ + [ + 0.5, + 0.417875 + ], + [ + 0.530361, + 0.442623 + ] + ], + [ + [ + 0.639634, + 0.512298 + ], + [ + 0.668975, + 0.502867 + ] + ], + [ + [ + 0.5, + 0.441526 + ], + [ + 0.52878, + 0.464508 + ] + ], + [ + [ + 0.47122, + 0.464508 + ], + [ + 0.5, + 0.441526 + ] + ], + [ + [ + 0.5, + 0.463496 + ], + [ + 0.526974, + 0.486968 + ] + ], + [ + [ + 0.473026, + 0.486968 + ], + [ + 0.5, + 0.463496 + ] + ], + [ + [ + 0.47644, + 0.539125 + ], + [ + 0.450446, + 0.513681 + ] + ], + [ + [ + 0.5, + 0.592402 + ], + [ + 0.523133, + 0.565435 + ] + ], + [ + [ + 0.524893, + 0.617408 + ], + [ + 0.547033, + 0.589481 + ] + ], + [ + [ + 0.577446, + 0.636535 + ], + [ + 0.597095, + 0.606403 + ] + ], + [ + [ + 0.754212, + 0.675253 + ], + [ + 0.720504, + 0.671903 + ] + ], + [ + [ + 0.720504, + 0.671903 + ], + [ + 0.682326, + 0.669164 + ] + ], + [ + [ + 0.63705, + 0.640103 + ], + [ + 0.602631, + 0.629461 + ] + ], + [ + [ + 0.739043, + 0.624847 + ], + [ + 0.702574, + 0.623861 + ] + ], + [ + [ + 0.808131, + 0.5908 + ], + [ + 0.778357, + 0.584325 + ] + ], + [ + [ + 0.622075, + 0.59842 + ], + [ + 0.602631, + 0.629461 + ] + ], + [ + [ + 0.668707, + 0.569215 + ], + [ + 0.637809, + 0.569945 + ] + ], + [ + [ + 0.673033, + 0.535028 + ], + [ + 0.644785, + 0.540977 + ] + ], + [ + [ + 0.901335, + 0.559052 + ], + [ + 0.911325, + 0.5267 + ] + ], + [ + [ + 0.930275, + 0.58033 + ], + [ + 0.941377, + 0.548864 + ] + ], + [ + [ + 0.988253, + 0.531283 + ], + [ + 0.972059, + 0.515354 + ] + ], + [ + [ + 0.250674, + 0.118071 + ], + [ + 0.271555, + 0.103546 + ] + ], + [ + [ + 0.271555, + 0.103546 + ], + [ + 0.290352, + 0.117067 + ] + ], + [ + [ + 0.287034, + 0.131845 + ], + [ + 0.269352, + 0.118058 + ] + ], + [ + [ + 0.260082, + 0.171847 + ], + [ + 0.283585, + 0.148775 + ] + ], + [ + [ + 0.256826, + 0.197208 + ], + [ + 0.28022, + 0.169805 + ] + ], + [ + [ + 0.437613, + 0.09577 + ], + [ + 0.41343, + 0.075575 + ] + ], + [ + [ + 0.336077, + 0.148051 + ], + [ + 0.304025, + 0.167719 + ] + ], + [ + [ + 0.330963, + 0.185453 + ], + [ + 0.365126, + 0.163671 + ] + ], + [ + [ + 0.533743, + 0.16026 + ], + [ + 0.566691, + 0.147081 + ] + ], + [ + [ + 0.601509, + 0.161839 + ], + [ + 0.632306, + 0.147781 + ] + ], + [ + [ + 0.43242, + 0.160566 + ], + [ + 0.465928, + 0.176336 + ] + ], + [ + [ + 0.398491, + 0.161839 + ], + [ + 0.431674, + 0.176723 + ] + ], + [ + [ + 0.275076, + 0.309956 + ], + [ + 0.300681, + 0.271195 + ] + ], + [ + [ + 0.245588, + 0.431405 + ], + [ + 0.261063, + 0.385941 + ] + ], + [ + [ + 0.143359, + 0.542462 + ], + [ + 0.169476, + 0.527685 + ] + ], + [ + [ + 0.157855, + 0.556834 + ], + [ + 0.18476, + 0.544286 + ] + ], + [ + [ + 0.068265, + 0.522807 + ], + [ + 0.0792, + 0.553515 + ] + ], + [ + [ + 0.0792, + 0.553515 + ], + [ + 0.089314, + 0.586671 + ] + ], + [ + [ + 0.147681, + 0.584686 + ], + [ + 0.173307, + 0.571665 + ] + ], + [ + [ + 0.12606, + 0.661515 + ], + [ + 0.148411, + 0.654493 + ] + ], + [ + [ + 0.163145, + 0.723995 + ], + [ + 0.164305, + 0.70142 + ] + ], + [ + [ + 0.23684, + 0.817415 + ], + [ + 0.225063, + 0.795187 + ] + ], + [ + [ + 0.19011, + 0.716656 + ], + [ + 0.214779, + 0.708216 + ] + ], + [ + [ + 0.274747, + 0.813019 + ], + [ + 0.254229, + 0.779213 + ] + ], + [ + [ + 0.415493, + 0.916547 + ], + [ + 0.374067, + 0.889117 + ] + ], + [ + [ + 0.374067, + 0.889117 + ], + [ + 0.338711, + 0.855978 + ] + ], + [ + [ + 0.419788, + 0.889358 + ], + [ + 0.383622, + 0.856695 + ] + ], + [ + [ + 0.245788, + 0.675253 + ], + [ + 0.218503, + 0.683718 + ] + ], + [ + [ + 0.287757, + 0.647377 + ], + [ + 0.325935, + 0.645079 + ] + ], + [ + [ + 0.267529, + 0.483262 + ], + [ + 0.276416, + 0.44444 + ] + ], + [ + [ + 0.333535, + 0.32811 + ], + [ + 0.366793, + 0.350878 + ] + ], + [ + [ + 0.34898, + 0.413728 + ], + [ + 0.371255, + 0.379294 + ] + ], + [ + [ + 0.312361, + 0.514694 + ], + [ + 0.319338, + 0.481434 + ] + ], + [ + [ + 0.334874, + 0.623639 + ], + [ + 0.370709, + 0.618808 + ] + ], + [ + [ + 0.347988, + 0.68752 + ], + [ + 0.384462, + 0.677115 + ] + ], + [ + [ + 0.460184, + 0.897681 + ], + [ + 0.423146, + 0.869976 + ] + ], + [ + [ + 0.672514, + 0.881391 + ], + [ + 0.70253, + 0.847472 + ] + ], + [ + [ + 0.773103, + 0.783692 + ], + [ + 0.756892, + 0.808607 + ] + ], + [ + [ + 0.809906, + 0.73861 + ], + [ + 0.79131, + 0.770216 + ] + ], + [ + [ + 0.785221, + 0.708216 + ], + [ + 0.765744, + 0.744894 + ] + ], + [ + [ + 0.762326, + 0.722897 + ], + [ + 0.741053, + 0.758701 + ] + ], + [ + [ + 0.576854, + 0.869976 + ], + [ + 0.539816, + 0.897681 + ] + ], + [ + [ + 0.754212, + 0.675253 + ], + [ + 0.732017, + 0.717423 + ] + ], + [ + [ + 0.860928, + 0.611292 + ], + [ + 0.836341, + 0.598859 + ] + ], + [ + [ + 0.469207, + 0.727973 + ], + [ + 0.436014, + 0.748378 + ] + ], + [ + [ + 0.532056, + 0.753948 + ], + [ + 0.5, + 0.729889 + ] + ], + [ + [ + 0.441477, + 0.695596 + ], + [ + 0.408457, + 0.71415 + ] + ], + [ + [ + 0.5, + 0.673444 + ], + [ + 0.470582, + 0.700434 + ] + ], + [ + [ + 0.369507, + 0.554684 + ], + [ + 0.343387, + 0.554373 + ] + ], + [ + [ + 0.450591, + 0.614939 + ], + [ + 0.429975, + 0.587166 + ] + ], + [ + [ + 0.368624, + 0.530317 + ], + [ + 0.343107, + 0.522611 + ] + ], + [ + [ + 0.350841, + 0.492846 + ], + [ + 0.319338, + 0.481434 + ] + ], + [ + [ + 0.379043, + 0.504525 + ], + [ + 0.393901, + 0.477574 + ] + ], + [ + [ + 0.466208, + 0.366365 + ], + [ + 0.431206, + 0.340937 + ] + ], + [ + [ + 0.405116, + 0.519472 + ], + [ + 0.422239, + 0.493264 + ] + ], + [ + [ + 0.46467, + 0.310525 + ], + [ + 0.429566, + 0.28432 + ] + ], + [ + [ + 0.5, + 0.30977 + ], + [ + 0.535611, + 0.282468 + ] + ], + [ + [ + 0.535516, + 0.254117 + ], + [ + 0.569836, + 0.227124 + ] + ], + [ + [ + 0.667018, + 0.165653 + ], + [ + 0.632306, + 0.147781 + ] + ], + [ + [ + 0.669474, + 0.235727 + ], + [ + 0.636813, + 0.204911 + ] + ], + [ + [ + 0.722614, + 0.193397 + ], + [ + 0.695975, + 0.167719 + ] + ], + [ + [ + 0.751388, + 0.132196 + ], + [ + 0.730648, + 0.118058 + ] + ], + [ + [ + 0.74784, + 0.103709 + ], + [ + 0.766842, + 0.117595 + ] + ], + [ + [ + 0.769604, + 0.252906 + ], + [ + 0.7977, + 0.28655 + ] + ], + [ + [ + 0.738937, + 0.385941 + ], + [ + 0.722054, + 0.343455 + ] + ], + [ + [ + 0.598425, + 0.37336 + ], + [ + 0.623078, + 0.406445 + ] + ], + [ + [ + 0.533792, + 0.366365 + ], + [ + 0.564503, + 0.396171 + ] + ], + [ + [ + 0.768438, + 0.517391 + ], + [ + 0.763714, + 0.474678 + ] + ], + [ + [ + 0.732471, + 0.483262 + ], + [ + 0.736742, + 0.522244 + ] + ], + [ + [ + 0.531623, + 0.418915 + ], + [ + 0.559572, + 0.445551 + ] + ], + [ + [ + 0.42697, + 0.516088 + ], + [ + 0.446842, + 0.489311 + ] + ], + [ + [ + 0.405116, + 0.519472 + ], + [ + 0.430491, + 0.539621 + ] + ], + [ + [ + 0.453921, + 0.564295 + ], + [ + 0.430491, + 0.539621 + ] + ], + [ + [ + 0.5, + 0.566028 + ], + [ + 0.52356, + 0.539125 + ] + ], + [ + [ + 0.591048, + 0.540655 + ], + [ + 0.614537, + 0.523682 + ] + ], + [ + [ + 0.5265, + 0.644005 + ], + [ + 0.549409, + 0.614939 + ] + ], + [ + [ + 0.552486, + 0.641045 + ], + [ + 0.573175, + 0.61159 + ] + ], + [ + [ + 0.597095, + 0.606403 + ], + [ + 0.616048, + 0.578959 + ] + ], + [ + [ + 0.652013, + 0.68752 + ], + [ + 0.615538, + 0.677115 + ] + ], + [ + [ + 0.702288, + 0.736023 + ], + [ + 0.726873, + 0.695265 + ] + ], + [ + [ + 0.682326, + 0.669164 + ], + [ + 0.644806, + 0.662831 + ] + ], + [ + [ + 0.79646, + 0.639224 + ], + [ + 0.769496, + 0.630467 + ] + ], + [ + [ + 0.702574, + 0.623861 + ], + [ + 0.665126, + 0.623639 + ] + ], + [ + [ + 0.836341, + 0.598859 + ], + [ + 0.808131, + 0.5908 + ] + ], + [ + [ + 0.778357, + 0.584325 + ], + [ + 0.748513, + 0.580464 + ] + ], + [ + [ + 0.702719, + 0.564759 + ], + [ + 0.668707, + 0.569215 + ] + ], + [ + [ + 0.688353, + 0.549111 + ], + [ + 0.721052, + 0.543894 + ] + ], + [ + [ + 0.95092, + 0.5743 + ], + [ + 0.962478, + 0.54476 + ] + ], + [ + [ + 0.293323, + 0.102491 + ], + [ + 0.273491, + 0.088896 + ] + ], + [ + [ + 0.225256, + 0.175659 + ], + [ + 0.245814, + 0.149596 + ] + ], + [ + [ + 0.245814, + 0.149596 + ], + [ + 0.266613, + 0.132294 + ] + ], + [ + [ + 0.263477, + 0.149231 + ], + [ + 0.287034, + 0.131845 + ] + ], + [ + [ + 0.283585, + 0.148775 + ], + [ + 0.311082, + 0.13133 + ] + ], + [ + [ + 0.28022, + 0.169805 + ], + [ + 0.307374, + 0.148343 + ] + ], + [ + [ + 0.27553, + 0.21801 + ], + [ + 0.30152, + 0.189409 + ] + ], + [ + [ + 0.30152, + 0.189409 + ], + [ + 0.332982, + 0.165653 + ] + ], + [ + [ + 0.339961, + 0.131229 + ], + [ + 0.307374, + 0.148343 + ] + ], + [ + [ + 0.330308, + 0.208874 + ], + [ + 0.363616, + 0.1818 + ] + ], + [ + [ + 0.402713, + 0.131662 + ], + [ + 0.375795, + 0.114187 + ] + ], + [ + [ + 0.467661, + 0.115348 + ], + [ + 0.49999, + 0.132218 + ] + ], + [ + [ + 0.533423, + 0.146896 + ], + [ + 0.565467, + 0.131938 + ] + ], + [ + [ + 0.660039, + 0.131229 + ], + [ + 0.685037, + 0.115737 + ] + ], + [ + [ + 0.533743, + 0.16026 + ], + [ + 0.5, + 0.176752 + ] + ], + [ + [ + 0.396758, + 0.20184 + ], + [ + 0.431674, + 0.176723 + ] + ], + [ + [ + 0.430164, + 0.227124 + ], + [ + 0.465399, + 0.199264 + ] + ], + [ + [ + 0.267078, + 0.417156 + ], + [ + 0.282836, + 0.375678 + ] + ], + [ + [ + 0.158348, + 0.512362 + ], + [ + 0.184098, + 0.498955 + ] + ], + [ + [ + 0.132702, + 0.574218 + ], + [ + 0.157855, + 0.556834 + ] + ], + [ + [ + 0.173307, + 0.571665 + ], + [ + 0.202691, + 0.56375 + ] + ], + [ + [ + 0.037522, + 0.54476 + ], + [ + 0.04908, + 0.5743 + ] + ], + [ + [ + 0.027941, + 0.515354 + ], + [ + 0.011747, + 0.531283 + ] + ], + [ + [ + 0.163659, + 0.598859 + ], + [ + 0.191869, + 0.5908 + ] + ], + [ + [ + 0.20869, + 0.770216 + ], + [ + 0.190094, + 0.73861 + ] + ], + [ + [ + 0.190094, + 0.73861 + ], + [ + 0.211823, + 0.731302 + ] + ], + [ + [ + 0.290505, + 0.85886 + ], + [ + 0.267652, + 0.826012 + ] + ], + [ + [ + 0.267652, + 0.826012 + ], + [ + 0.249022, + 0.796855 + ] + ], + [ + [ + 0.37823, + 0.874938 + ], + [ + 0.345581, + 0.838555 + ] + ], + [ + [ + 0.383622, + 0.856695 + ], + [ + 0.352422, + 0.81816 + ] + ], + [ + [ + 0.287757, + 0.647377 + ], + [ + 0.25201, + 0.650015 + ] + ], + [ + [ + 0.308015, + 0.603525 + ], + [ + 0.271874, + 0.601536 + ] + ], + [ + [ + 0.335422, + 0.734115 + ], + [ + 0.310205, + 0.693457 + ] + ], + [ + [ + 0.280494, + 0.506683 + ], + [ + 0.287181, + 0.469667 + ] + ], + [ + [ + 0.398376, + 0.345134 + ], + [ + 0.364748, + 0.321532 + ] + ], + [ + [ + 0.331413, + 0.447966 + ], + [ + 0.34898, + 0.413728 + ] + ], + [ + [ + 0.340904, + 0.471542 + ], + [ + 0.356345, + 0.439287 + ] + ], + [ + [ + 0.310205, + 0.693457 + ], + [ + 0.347988, + 0.68752 + ] + ], + [ + [ + 0.384462, + 0.677115 + ], + [ + 0.41782, + 0.66202 + ] + ], + [ + [ + 0.363263, + 0.77242 + ], + [ + 0.400773, + 0.762925 + ] + ], + [ + [ + 0.461792, + 0.87747 + ], + [ + 0.426616, + 0.847025 + ] + ], + [ + [ + 0.582602, + 0.90411 + ], + [ + 0.62177, + 0.874938 + ] + ], + [ + [ + 0.76316, + 0.817415 + ], + [ + 0.774937, + 0.795187 + ] + ], + [ + [ + 0.814785, + 0.758109 + ], + [ + 0.82791, + 0.722083 + ] + ], + [ + [ + 0.82791, + 0.722083 + ], + [ + 0.80989, + 0.716656 + ] + ], + [ + [ + 0.917271, + 0.603469 + ], + [ + 0.89743, + 0.607055 + ] + ], + [ + [ + 0.785221, + 0.708216 + ], + [ + 0.758671, + 0.699801 + ] + ], + [ + [ + 0.89743, + 0.607055 + ], + [ + 0.875931, + 0.599667 + ] + ], + [ + [ + 0.68862, + 0.816485 + ], + [ + 0.661289, + 0.855978 + ] + ], + [ + [ + 0.647578, + 0.81816 + ], + [ + 0.616377, + 0.856695 + ] + ], + [ + [ + 0.570657, + 0.821564 + ], + [ + 0.534165, + 0.801348 + ] + ], + [ + [ + 0.568375, + 0.795845 + ], + [ + 0.533143, + 0.77763 + ] + ], + [ + [ + 0.641801, + 0.795594 + ], + [ + 0.602675, + 0.786112 + ] + ], + [ + [ + 0.5, + 0.702146 + ], + [ + 0.469207, + 0.727973 + ] + ], + [ + [ + 0.444458, + 0.668171 + ], + [ + 0.422554, + 0.636535 + ] + ], + [ + [ + 0.407078, + 0.583879 + ], + [ + 0.388145, + 0.560448 + ] + ], + [ + [ + 0.389112, + 0.542238 + ], + [ + 0.368624, + 0.530317 + ] + ], + [ + [ + 0.371697, + 0.484507 + ], + [ + 0.388243, + 0.455771 + ] + ], + [ + [ + 0.405569, + 0.400545 + ], + [ + 0.433153, + 0.368986 + ] + ], + [ + [ + 0.429566, + 0.28432 + ], + [ + 0.396246, + 0.25777 + ] + ], + [ + [ + 0.500003, + 0.224998 + ], + [ + 0.465399, + 0.199264 + ] + ], + [ + [ + 0.535611, + 0.282468 + ], + [ + 0.570334, + 0.255524 + ] + ], + [ + [ + 0.636395, + 0.261117 + ], + [ + 0.603556, + 0.228945 + ] + ], + [ + [ + 0.669692, + 0.208874 + ], + [ + 0.636384, + 0.1818 + ] + ], + [ + [ + 0.695975, + 0.167719 + ], + [ + 0.663923, + 0.148051 + ] + ], + [ + [ + 0.72447, + 0.21801 + ], + [ + 0.69848, + 0.189409 + ] + ], + [ + [ + 0.725502, + 0.245202 + ], + [ + 0.750214, + 0.281101 + ] + ], + [ + [ + 0.774078, + 0.284195 + ], + [ + 0.748199, + 0.249597 + ] + ], + [ + [ + 0.765672, + 0.226029 + ], + [ + 0.790363, + 0.255673 + ] + ], + [ + [ + 0.767013, + 0.359819 + ], + [ + 0.805802, + 0.401517 + ] + ], + [ + [ + 0.667875, + 0.297256 + ], + [ + 0.695752, + 0.335601 + ] + ], + [ + [ + 0.635252, + 0.321532 + ], + [ + 0.667875, + 0.297256 + ] + ], + [ + [ + 0.775419, + 0.449362 + ], + [ + 0.809209, + 0.452439 + ] + ], + [ + [ + 0.717164, + 0.375678 + ], + [ + 0.732922, + 0.417156 + ] + ], + [ + [ + 0.809209, + 0.452439 + ], + [ + 0.815902, + 0.498955 + ] + ], + [ + [ + 0.815902, + 0.498955 + ], + [ + 0.841652, + 0.512362 + ] + ], + [ + [ + 0.468378, + 0.418915 + ], + [ + 0.5, + 0.392533 + ] + ], + [ + [ + 0.469639, + 0.442623 + ], + [ + 0.5, + 0.417875 + ] + ], + [ + [ + 0.78421, + 0.538767 + ], + [ + 0.783311, + 0.494834 + ] + ], + [ + [ + 0.750892, + 0.499072 + ], + [ + 0.752411, + 0.539539 + ] + ], + [ + [ + 0.815239, + 0.544286 + ], + [ + 0.842145, + 0.556834 + ] + ], + [ + [ + 0.611758, + 0.455771 + ], + [ + 0.628303, + 0.484507 + ] + ], + [ + [ + 0.562153, + 0.421761 + ], + [ + 0.586486, + 0.449947 + ] + ], + [ + [ + 0.582242, + 0.47177 + ], + [ + 0.600466, + 0.498459 + ] + ], + [ + [ + 0.577761, + 0.493264 + ], + [ + 0.594884, + 0.519472 + ] + ], + [ + [ + 0.453173, + 0.539117 + ], + [ + 0.476867, + 0.565435 + ] + ], + [ + [ + 0.546827, + 0.539117 + ], + [ + 0.57303, + 0.516088 + ] + ], + [ + [ + 0.476867, + 0.565435 + ], + [ + 0.5, + 0.592402 + ] + ], + [ + [ + 0.546079, + 0.564295 + ], + [ + 0.569509, + 0.539621 + ] + ], + [ + [ + 0.547033, + 0.589481 + ], + [ + 0.568655, + 0.563245 + ] + ], + [ + [ + 0.591542, + 0.71415 + ], + [ + 0.558523, + 0.695596 + ] + ], + [ + [ + 0.621639, + 0.702497 + ], + [ + 0.587003, + 0.68793 + ] + ], + [ + [ + 0.665126, + 0.623639 + ], + [ + 0.629291, + 0.618808 + ] + ], + [ + [ + 0.748513, + 0.580464 + ], + [ + 0.715998, + 0.581871 + ] + ], + [ + [ + 0.735535, + 0.560787 + ], + [ + 0.702719, + 0.564759 + ] + ], + [ + [ + 0.766339, + 0.559817 + ], + [ + 0.797309, + 0.56375 + ] + ], + [ + [ + 0.875931, + 0.599667 + ], + [ + 0.852319, + 0.584686 + ] + ], + [ + [ + 0.875931, + 0.599667 + ], + [ + 0.883569, + 0.565813 + ] + ], + [ + [ + 0.883569, + 0.565813 + ], + [ + 0.89134, + 0.53052 + ] + ], + [ + [ + 0.941377, + 0.548864 + ], + [ + 0.952995, + 0.51877 + ] + ], + [ + [ + 0.99708, + 0.51199 + ], + [ + 0.988253, + 0.531283 + ] + ], + [ + [ + 0.231821, + 0.131826 + ], + [ + 0.250674, + 0.118071 + ] + ], + [ + [ + 0.2023, + 0.28655 + ], + [ + 0.230396, + 0.252906 + ] + ], + [ + [ + 0.230396, + 0.252906 + ], + [ + 0.254107, + 0.222378 + ] + ], + [ + [ + 0.277386, + 0.193397 + ], + [ + 0.304025, + 0.167719 + ] + ], + [ + [ + 0.249786, + 0.281101 + ], + [ + 0.274498, + 0.245202 + ] + ], + [ + [ + 0.274498, + 0.245202 + ], + [ + 0.300347, + 0.213363 + ] + ], + [ + [ + 0.336077, + 0.148051 + ], + [ + 0.371296, + 0.131389 + ] + ], + [ + [ + 0.43242, + 0.160566 + ], + [ + 0.400227, + 0.147417 + ] + ], + [ + [ + 0.499926, + 0.115745 + ], + [ + 0.531785, + 0.096088 + ] + ], + [ + [ + 0.500062, + 0.146787 + ], + [ + 0.46708, + 0.132105 + ] + ], + [ + [ + 0.590498, + 0.095408 + ], + [ + 0.613299, + 0.076986 + ] + ], + [ + [ + 0.564, + 0.11491 + ], + [ + 0.590498, + 0.095408 + ] + ], + [ + [ + 0.565467, + 0.131938 + ], + [ + 0.594159, + 0.114389 + ] + ], + [ + [ + 0.681167, + 0.10062 + ], + [ + 0.70388, + 0.088017 + ] + ], + [ + [ + 0.655527, + 0.114707 + ], + [ + 0.681167, + 0.10062 + ] + ], + [ + [ + 0.728445, + 0.103546 + ], + [ + 0.746643, + 0.089229 + ] + ], + [ + [ + 0.709648, + 0.117067 + ], + [ + 0.728445, + 0.103546 + ] + ], + [ + [ + 0.568326, + 0.176723 + ], + [ + 0.601509, + 0.161839 + ] + ], + [ + [ + 0.363187, + 0.204911 + ], + [ + 0.397378, + 0.178717 + ] + ], + [ + [ + 0.363605, + 0.261117 + ], + [ + 0.396444, + 0.228945 + ] + ], + [ + [ + 0.261063, + 0.385941 + ], + [ + 0.277946, + 0.343455 + ] + ], + [ + [ + 0.249108, + 0.499072 + ], + [ + 0.255936, + 0.458173 + ] + ], + [ + [ + 0.047005, + 0.51877 + ], + [ + 0.058623, + 0.548864 + ] + ], + [ + [ + 0.058623, + 0.548864 + ], + [ + 0.069725, + 0.58033 + ] + ], + [ + [ + 0.202691, + 0.56375 + ], + [ + 0.233661, + 0.559817 + ] + ], + [ + [ + 0.09835, + 0.620475 + ], + [ + 0.117854, + 0.620779 + ] + ], + [ + [ + 0.164305, + 0.70142 + ], + [ + 0.148411, + 0.654493 + ] + ], + [ + [ + 0.148411, + 0.654493 + ], + [ + 0.175602, + 0.64616 + ] + ], + [ + [ + 0.175602, + 0.64616 + ], + [ + 0.203541, + 0.639224 + ] + ], + [ + [ + 0.243108, + 0.808607 + ], + [ + 0.226896, + 0.783692 + ] + ], + [ + [ + 0.226896, + 0.783692 + ], + [ + 0.209089, + 0.753406 + ] + ], + [ + [ + 0.37052, + 0.901265 + ], + [ + 0.332847, + 0.869627 + ] + ], + [ + [ + 0.332847, + 0.869627 + ], + [ + 0.30443, + 0.834032 + ] + ], + [ + [ + 0.338711, + 0.855978 + ], + [ + 0.31138, + 0.816485 + ] + ], + [ + [ + 0.345581, + 0.838555 + ], + [ + 0.318022, + 0.796554 + ] + ], + [ + [ + 0.352422, + 0.81816 + ], + [ + 0.324057, + 0.775962 + ] + ], + [ + [ + 0.223542, + 0.657377 + ], + [ + 0.197321, + 0.666673 + ] + ], + [ + [ + 0.271874, + 0.601536 + ], + [ + 0.239992, + 0.60426 + ] + ], + [ + [ + 0.297281, + 0.564759 + ], + [ + 0.264465, + 0.560787 + ] + ], + [ + [ + 0.276416, + 0.44444 + ], + [ + 0.289831, + 0.405275 + ] + ], + [ + [ + 0.343387, + 0.554373 + ], + [ + 0.311648, + 0.549111 + ] + ], + [ + [ + 0.377925, + 0.59842 + ], + [ + 0.362191, + 0.569945 + ] + ], + [ + [ + 0.34138, + 0.712004 + ], + [ + 0.378361, + 0.702497 + ] + ], + [ + [ + 0.335422, + 0.734115 + ], + [ + 0.372823, + 0.727407 + ] + ], + [ + [ + 0.5, + 0.915521 + ], + [ + 0.460184, + 0.897681 + ] + ], + [ + [ + 0.584508, + 0.916547 + ], + [ + 0.625933, + 0.889117 + ] + ], + [ + [ + 0.70253, + 0.847472 + ], + [ + 0.725253, + 0.813019 + ] + ], + [ + [ + 0.769491, + 0.766143 + ], + [ + 0.750978, + 0.796855 + ] + ], + [ + [ + 0.836855, + 0.723995 + ], + [ + 0.835695, + 0.70142 + ] + ], + [ + [ + 0.741053, + 0.758701 + ], + [ + 0.719048, + 0.79515 + ] + ], + [ + [ + 0.830383, + 0.67382 + ], + [ + 0.802679, + 0.666673 + ] + ], + [ + [ + 0.736606, + 0.738336 + ], + [ + 0.713375, + 0.775122 + ] + ], + [ + [ + 0.675942, + 0.775962 + ], + [ + 0.647578, + 0.81816 + ] + ], + [ + [ + 0.606443, + 0.810872 + ], + [ + 0.573384, + 0.847025 + ] + ], + [ + [ + 0.535237, + 0.827433 + ], + [ + 0.5, + 0.855508 + ] + ], + [ + [ + 0.5, + 0.855508 + ], + [ + 0.464763, + 0.827433 + ] + ], + [ + [ + 0.5, + 0.779463 + ], + [ + 0.465835, + 0.801348 + ] + ], + [ + [ + 0.533143, + 0.77763 + ], + [ + 0.5, + 0.755914 + ] + ], + [ + [ + 0.438624, + 0.722601 + ], + [ + 0.404337, + 0.739531 + ] + ], + [ + [ + 0.529418, + 0.700434 + ], + [ + 0.5, + 0.673444 + ] + ], + [ + [ + 0.422554, + 0.636535 + ], + [ + 0.402905, + 0.606403 + ] + ], + [ + [ + 0.5, + 0.673444 + ], + [ + 0.4735, + 0.644005 + ] + ], + [ + [ + 0.433153, + 0.368986 + ], + [ + 0.398376, + 0.345134 + ] + ], + [ + [ + 0.4095, + 0.42614 + ], + [ + 0.435497, + 0.396171 + ] + ], + [ + [ + 0.431206, + 0.340937 + ], + [ + 0.396827, + 0.316418 + ] + ], + [ + [ + 0.465289, + 0.33844 + ], + [ + 0.430044, + 0.312753 + ] + ], + [ + [ + 0.443464, + 0.467334 + ], + [ + 0.469639, + 0.442623 + ] + ], + [ + [ + 0.500002, + 0.253129 + ], + [ + 0.464855, + 0.226154 + ] + ], + [ + [ + 0.603556, + 0.228945 + ], + [ + 0.569123, + 0.199884 + ] + ], + [ + [ + 0.569956, + 0.312753 + ], + [ + 0.603679, + 0.287274 + ] + ], + [ + [ + 0.636384, + 0.1818 + ], + [ + 0.601509, + 0.161839 + ] + ], + [ + [ + 0.669037, + 0.185453 + ], + [ + 0.634874, + 0.163671 + ] + ], + [ + [ + 0.725502, + 0.245202 + ], + [ + 0.699653, + 0.213363 + ] + ], + [ + [ + 0.769604, + 0.252906 + ], + [ + 0.745893, + 0.222378 + ] + ], + [ + [ + 0.749326, + 0.118071 + ], + [ + 0.728445, + 0.103546 + ] + ], + [ + [ + 0.761894, + 0.200631 + ], + [ + 0.78463, + 0.22932 + ] + ], + [ + [ + 0.725863, + 0.276527 + ], + [ + 0.750226, + 0.315493 + ] + ], + [ + [ + 0.695752, + 0.335601 + ], + [ + 0.717164, + 0.375678 + ] + ], + [ + [ + 0.658025, + 0.386595 + ], + [ + 0.691812, + 0.366345 + ] + ], + [ + [ + 0.691812, + 0.366345 + ], + [ + 0.710169, + 0.405275 + ] + ], + [ + [ + 0.775419, + 0.449362 + ], + [ + 0.783311, + 0.494834 + ] + ], + [ + [ + 0.801079, + 0.518188 + ], + [ + 0.830524, + 0.527685 + ] + ], + [ + [ + 0.643655, + 0.439287 + ], + [ + 0.623078, + 0.406445 + ] + ], + [ + [ + 0.680662, + 0.481434 + ], + [ + 0.687639, + 0.514694 + ] + ], + [ + [ + 0.5905, + 0.42614 + ], + [ + 0.611758, + 0.455771 + ] + ], + [ + [ + 0.649159, + 0.492846 + ], + [ + 0.680662, + 0.481434 + ] + ], + [ + [ + 0.530361, + 0.442623 + ], + [ + 0.556536, + 0.467334 + ] + ], + [ + [ + 0.52878, + 0.464508 + ], + [ + 0.553158, + 0.489311 + ] + ], + [ + [ + 0.526974, + 0.486968 + ], + [ + 0.549554, + 0.513681 + ] + ], + [ + [ + 0.474963, + 0.512405 + ], + [ + 0.5, + 0.539257 + ] + ], + [ + [ + 0.431345, + 0.563245 + ], + [ + 0.452967, + 0.589481 + ] + ], + [ + [ + 0.5, + 0.645097 + ], + [ + 0.524893, + 0.617408 + ] + ], + [ + [ + 0.5, + 0.673444 + ], + [ + 0.5265, + 0.644005 + ] + ], + [ + [ + 0.573175, + 0.61159 + ], + [ + 0.592922, + 0.583879 + ] + ], + [ + [ + 0.664577, + 0.734115 + ], + [ + 0.627177, + 0.727407 + ] + ], + [ + [ + 0.720504, + 0.671903 + ], + [ + 0.696355, + 0.715518 + ] + ], + [ + [ + 0.74799, + 0.650015 + ], + [ + 0.712242, + 0.647377 + ] + ], + [ + [ + 0.646488, + 0.586429 + ], + [ + 0.629291, + 0.618808 + ] + ], + [ + [ + 0.735535, + 0.560787 + ], + [ + 0.766339, + 0.559817 + ] + ], + [ + [ + 0.797309, + 0.56375 + ], + [ + 0.826693, + 0.571665 + ] + ], + [ + [ + 0.910686, + 0.586671 + ], + [ + 0.9208, + 0.553515 + ] + ], + [ + [ + 0.917271, + 0.603469 + ], + [ + 0.92578, + 0.58772 + ] + ], + [ + [ + 0.92578, + 0.58772 + ], + [ + 0.910686, + 0.586671 + ] + ], + [ + [ + 0.977738, + 0.538836 + ], + [ + 0.952995, + 0.51877 + ] + ], + [ + [ + 0.23395, + 0.103426 + ], + [ + 0.253357, + 0.089229 + ] + ], + [ + [ + 0.271555, + 0.103546 + ], + [ + 0.253357, + 0.089229 + ] + ], + [ + [ + 0.344473, + 0.114707 + ], + [ + 0.318833, + 0.10062 + ] + ], + [ + [ + 0.251801, + 0.249597 + ], + [ + 0.27553, + 0.21801 + ] + ], + [ + [ + 0.176198, + 0.3884 + ], + [ + 0.208087, + 0.360786 + ] + ], + [ + [ + 0.249774, + 0.315493 + ], + [ + 0.274137, + 0.276527 + ] + ], + [ + [ + 0.339961, + 0.131229 + ], + [ + 0.314963, + 0.115737 + ] + ], + [ + [ + 0.371296, + 0.131389 + ], + [ + 0.344473, + 0.114707 + ] + ], + [ + [ + 0.434533, + 0.131938 + ], + [ + 0.40584, + 0.114389 + ] + ], + [ + [ + 0.531785, + 0.096088 + ], + [ + 0.560699, + 0.075575 + ] + ], + [ + [ + 0.466577, + 0.146896 + ], + [ + 0.5, + 0.160438 + ] + ], + [ + [ + 0.330526, + 0.235727 + ], + [ + 0.363187, + 0.204911 + ] + ], + [ + [ + 0.301993, + 0.30367 + ], + [ + 0.331127, + 0.265825 + ] + ], + [ + [ + 0.331127, + 0.265825 + ], + [ + 0.364044, + 0.291522 + ] + ], + [ + [ + 0.304248, + 0.335601 + ], + [ + 0.332126, + 0.297256 + ] + ], + [ + [ + 0.190791, + 0.452439 + ], + [ + 0.224581, + 0.449362 + ] + ], + [ + [ + 0.184098, + 0.498955 + ], + [ + 0.190791, + 0.452439 + ] + ], + [ + [ + 0.126073, + 0.537991 + ], + [ + 0.132702, + 0.574218 + ] + ], + [ + [ + 0.116431, + 0.565813 + ], + [ + 0.124069, + 0.599667 + ] + ], + [ + [ + 0.047005, + 0.51877 + ], + [ + 0.022262, + 0.538836 + ] + ], + [ + [ + 0.011747, + 0.531283 + ], + [ + 0.00292, + 0.51199 + ] + ], + [ + [ + 0.117854, + 0.620779 + ], + [ + 0.139071, + 0.611292 + ] + ], + [ + [ + 0.202395, + 0.78162 + ], + [ + 0.20869, + 0.770216 + ] + ], + [ + [ + 0.260111, + 0.836569 + ], + [ + 0.243108, + 0.808607 + ] + ], + [ + [ + 0.249022, + 0.796855 + ], + [ + 0.230509, + 0.766143 + ] + ], + [ + [ + 0.31138, + 0.816485 + ], + [ + 0.286625, + 0.775122 + ] + ], + [ + [ + 0.318022, + 0.796554 + ], + [ + 0.292125, + 0.755652 + ] + ], + [ + [ + 0.324057, + 0.775962 + ], + [ + 0.297712, + 0.736023 + ] + ], + [ + [ + 0.239992, + 0.60426 + ], + [ + 0.211587, + 0.611361 + ] + ], + [ + [ + 0.303645, + 0.715518 + ], + [ + 0.279496, + 0.671903 + ] + ], + [ + [ + 0.264465, + 0.560787 + ], + [ + 0.233661, + 0.559817 + ] + ], + [ + [ + 0.279496, + 0.671903 + ], + [ + 0.317674, + 0.669164 + ] + ], + [ + [ + 0.297426, + 0.623861 + ], + [ + 0.334874, + 0.623639 + ] + ], + [ + [ + 0.287181, + 0.469667 + ], + [ + 0.298501, + 0.432598 + ] + ], + [ + [ + 0.364748, + 0.321532 + ], + [ + 0.332126, + 0.297256 + ] + ], + [ + [ + 0.308417, + 0.457998 + ], + [ + 0.322565, + 0.422433 + ] + ], + [ + [ + 0.299215, + 0.493024 + ], + [ + 0.308417, + 0.457998 + ] + ], + [ + [ + 0.376922, + 0.406445 + ], + [ + 0.401575, + 0.37336 + ] + ], + [ + [ + 0.297281, + 0.564759 + ], + [ + 0.331293, + 0.569215 + ] + ], + [ + [ + 0.397369, + 0.629461 + ], + [ + 0.377925, + 0.59842 + ] + ], + [ + [ + 0.378361, + 0.702497 + ], + [ + 0.412997, + 0.68793 + ] + ], + [ + [ + 0.324057, + 0.775962 + ], + [ + 0.363263, + 0.77242 + ] + ], + [ + [ + 0.5, + 0.900318 + ], + [ + 0.461792, + 0.87747 + ] + ], + [ + [ + 0.709495, + 0.85886 + ], + [ + 0.732348, + 0.826012 + ] + ], + [ + [ + 0.765744, + 0.744894 + ], + [ + 0.745771, + 0.779213 + ] + ], + [ + [ + 0.80989, + 0.716656 + ], + [ + 0.785221, + 0.708216 + ] + ], + [ + [ + 0.830383, + 0.67382 + ], + [ + 0.80989, + 0.716656 + ] + ], + [ + [ + 0.887153, + 0.639722 + ], + [ + 0.867855, + 0.636703 + ] + ], + [ + [ + 0.867855, + 0.636703 + ], + [ + 0.844525, + 0.626319 + ] + ], + [ + [ + 0.713375, + 0.775122 + ], + [ + 0.68862, + 0.816485 + ] + ], + [ + [ + 0.616377, + 0.856695 + ], + [ + 0.580212, + 0.889358 + ] + ], + [ + [ + 0.702288, + 0.736023 + ], + [ + 0.675942, + 0.775962 + ] + ], + [ + [ + 0.536529, + 0.853576 + ], + [ + 0.5, + 0.87977 + ] + ], + [ + [ + 0.5, + 0.87977 + ], + [ + 0.463471, + 0.853576 + ] + ], + [ + [ + 0.824398, + 0.64616 + ], + [ + 0.79646, + 0.639224 + ] + ], + [ + [ + 0.465835, + 0.801348 + ], + [ + 0.429343, + 0.821564 + ] + ], + [ + [ + 0.436014, + 0.748378 + ], + [ + 0.400773, + 0.762925 + ] + ], + [ + [ + 0.675942, + 0.775962 + ], + [ + 0.636738, + 0.77242 + ] + ], + [ + [ + 0.670235, + 0.755039 + ], + [ + 0.632036, + 0.750203 + ] + ], + [ + [ + 0.472003, + 0.672063 + ], + [ + 0.441477, + 0.695596 + ] + ], + [ + [ + 0.447513, + 0.641045 + ], + [ + 0.426825, + 0.61159 + ] + ], + [ + [ + 0.429975, + 0.587166 + ], + [ + 0.40935, + 0.562043 + ] + ], + [ + [ + 0.343107, + 0.522611 + ], + [ + 0.312361, + 0.514694 + ] + ], + [ + [ + 0.360366, + 0.512298 + ], + [ + 0.331025, + 0.502867 + ] + ], + [ + [ + 0.363956, + 0.462979 + ], + [ + 0.382584, + 0.431999 + ] + ], + [ + [ + 0.413514, + 0.449947 + ], + [ + 0.437847, + 0.421761 + ] + ], + [ + [ + 0.422239, + 0.493264 + ], + [ + 0.443464, + 0.467334 + ] + ], + [ + [ + 0.5, + 0.337617 + ], + [ + 0.46467, + 0.310525 + ] + ], + [ + [ + 0.5, + 0.28126 + ], + [ + 0.535516, + 0.254117 + ] + ], + [ + [ + 0.603755, + 0.25777 + ], + [ + 0.569836, + 0.227124 + ] + ], + [ + [ + 0.603679, + 0.287274 + ], + [ + 0.636395, + 0.261117 + ] + ], + [ + [ + 0.69848, + 0.189409 + ], + [ + 0.667018, + 0.165653 + ] + ], + [ + [ + 0.725863, + 0.276527 + ], + [ + 0.699823, + 0.240364 + ] + ], + [ + [ + 0.71978, + 0.169805 + ], + [ + 0.692626, + 0.148343 + ] + ], + [ + [ + 0.748199, + 0.249597 + ], + [ + 0.72447, + 0.21801 + ] + ], + [ + [ + 0.765672, + 0.226029 + ], + [ + 0.743174, + 0.197208 + ] + ], + [ + [ + 0.76605, + 0.103426 + ], + [ + 0.746643, + 0.089229 + ] + ], + [ + [ + 0.757876, + 0.17378 + ], + [ + 0.779693, + 0.20386 + ] + ], + [ + [ + 0.601624, + 0.345134 + ], + [ + 0.635252, + 0.321532 + ] + ], + [ + [ + 0.628745, + 0.379294 + ], + [ + 0.663548, + 0.358014 + ] + ], + [ + [ + 0.754412, + 0.431405 + ], + [ + 0.763714, + 0.474678 + ] + ], + [ + [ + 0.830524, + 0.527685 + ], + [ + 0.856641, + 0.542462 + ] + ], + [ + [ + 0.594431, + 0.400545 + ], + [ + 0.617416, + 0.431999 + ] + ], + [ + [ + 0.752411, + 0.539539 + ], + [ + 0.78421, + 0.538767 + ] + ], + [ + [ + 0.78421, + 0.538767 + ], + [ + 0.815239, + 0.544286 + ] + ], + [ + [ + 0.617416, + 0.431999 + ], + [ + 0.636044, + 0.462979 + ] + ], + [ + [ + 0.620957, + 0.504525 + ], + [ + 0.649159, + 0.492846 + ] + ], + [ + [ + 0.614537, + 0.523682 + ], + [ + 0.639634, + 0.512298 + ] + ], + [ + [ + 0.446842, + 0.489311 + ], + [ + 0.47122, + 0.464508 + ] + ], + [ + [ + 0.450446, + 0.513681 + ], + [ + 0.473026, + 0.486968 + ] + ], + [ + [ + 0.5, + 0.539257 + ], + [ + 0.525038, + 0.512405 + ] + ], + [ + [ + 0.475107, + 0.617408 + ], + [ + 0.452967, + 0.589481 + ] + ], + [ + [ + 0.5, + 0.618421 + ], + [ + 0.523621, + 0.591481 + ] + ], + [ + [ + 0.59065, + 0.562043 + ], + [ + 0.610888, + 0.542238 + ] + ], + [ + [ + 0.592922, + 0.583879 + ], + [ + 0.611855, + 0.560448 + ] + ], + [ + [ + 0.58218, + 0.66202 + ], + [ + 0.602631, + 0.629461 + ] + ], + [ + [ + 0.615538, + 0.677115 + ], + [ + 0.58218, + 0.66202 + ] + ], + [ + [ + 0.670235, + 0.755039 + ], + [ + 0.696355, + 0.715518 + ] + ], + [ + [ + 0.689795, + 0.693457 + ], + [ + 0.652013, + 0.68752 + ] + ], + [ + [ + 0.674065, + 0.645079 + ], + [ + 0.63705, + 0.640103 + ] + ], + [ + [ + 0.788413, + 0.611361 + ], + [ + 0.760008, + 0.60426 + ] + ], + [ + [ + 0.715998, + 0.581871 + ], + [ + 0.680641, + 0.585575 + ] + ], + [ + [ + 0.656613, + 0.554373 + ], + [ + 0.630493, + 0.554684 + ] + ], + [ + [ + 0.673033, + 0.535028 + ], + [ + 0.705042, + 0.52845 + ] + ], + [ + [ + 0.826693, + 0.571665 + ], + [ + 0.852319, + 0.584686 + ] + ], + [ + [ + 0.9208, + 0.553515 + ], + [ + 0.931735, + 0.522807 + ] + ], + [ + [ + 0.229376, + 0.149916 + ], + [ + 0.248612, + 0.132196 + ] + ], + [ + [ + 0.248612, + 0.132196 + ], + [ + 0.269352, + 0.118058 + ] + ], + [ + [ + 0.349456, + 0.098008 + ], + [ + 0.322697, + 0.085741 + ] + ], + [ + [ + 0.254107, + 0.222378 + ], + [ + 0.277386, + 0.193397 + ] + ], + [ + [ + 0.185259, + 0.353575 + ], + [ + 0.21909, + 0.32009 + ] + ], + [ + [ + 0.21909, + 0.32009 + ], + [ + 0.249786, + 0.281101 + ] + ], + [ + [ + 0.232987, + 0.359819 + ], + [ + 0.21909, + 0.32009 + ] + ], + [ + [ + 0.300347, + 0.213363 + ], + [ + 0.330963, + 0.185453 + ] + ], + [ + [ + 0.375795, + 0.114187 + ], + [ + 0.349456, + 0.098008 + ] + ], + [ + [ + 0.400227, + 0.147417 + ], + [ + 0.371296, + 0.131389 + ] + ], + [ + [ + 0.40584, + 0.114389 + ], + [ + 0.381057, + 0.095866 + ] + ], + [ + [ + 0.433309, + 0.147081 + ], + [ + 0.402713, + 0.131662 + ] + ], + [ + [ + 0.53292, + 0.132105 + ], + [ + 0.564, + 0.11491 + ] + ], + [ + [ + 0.650544, + 0.098008 + ], + [ + 0.677302, + 0.085741 + ] + ], + [ + [ + 0.624205, + 0.114187 + ], + [ + 0.650544, + 0.098008 + ] + ], + [ + [ + 0.628704, + 0.131389 + ], + [ + 0.655527, + 0.114707 + ] + ], + [ + [ + 0.534072, + 0.176336 + ], + [ + 0.56758, + 0.160566 + ] + ], + [ + [ + 0.466257, + 0.16026 + ], + [ + 0.5, + 0.176752 + ] + ], + [ + [ + 0.500122, + 0.199313 + ], + [ + 0.534072, + 0.176336 + ] + ], + [ + [ + 0.363348, + 0.231764 + ], + [ + 0.396758, + 0.20184 + ] + ], + [ + [ + 0.25537, + 0.351726 + ], + [ + 0.275076, + 0.309956 + ] + ], + [ + [ + 0.216688, + 0.494834 + ], + [ + 0.224581, + 0.449362 + ] + ], + [ + [ + 0.198921, + 0.518188 + ], + [ + 0.203275, + 0.472363 + ] + ], + [ + [ + 0.247589, + 0.539539 + ], + [ + 0.249108, + 0.499072 + ] + ], + [ + [ + 0.124069, + 0.599667 + ], + [ + 0.147681, + 0.584686 + ] + ], + [ + [ + 0.10257, + 0.607055 + ], + [ + 0.124069, + 0.599667 + ] + ], + [ + [ + 0.112847, + 0.639722 + ], + [ + 0.132145, + 0.636703 + ] + ], + [ + [ + 0.155475, + 0.626319 + ], + [ + 0.182929, + 0.618378 + ] + ], + [ + [ + 0.19011, + 0.716656 + ], + [ + 0.169618, + 0.67382 + ] + ], + [ + [ + 0.169618, + 0.67382 + ], + [ + 0.197321, + 0.666673 + ] + ], + [ + [ + 0.30443, + 0.834032 + ], + [ + 0.280952, + 0.79515 + ] + ], + [ + [ + 0.263394, + 0.738336 + ], + [ + 0.241329, + 0.699801 + ] + ], + [ + [ + 0.292125, + 0.755652 + ], + [ + 0.267983, + 0.717423 + ] + ], + [ + [ + 0.279496, + 0.671903 + ], + [ + 0.245788, + 0.675253 + ] + ], + [ + [ + 0.329765, + 0.755039 + ], + [ + 0.303645, + 0.715518 + ] + ], + [ + [ + 0.319359, + 0.585575 + ], + [ + 0.284003, + 0.581871 + ] + ], + [ + [ + 0.289831, + 0.405275 + ], + [ + 0.308188, + 0.366345 + ] + ], + [ + [ + 0.396827, + 0.316418 + ], + [ + 0.364044, + 0.291522 + ] + ], + [ + [ + 0.322565, + 0.422433 + ], + [ + 0.341975, + 0.386595 + ] + ], + [ + [ + 0.294957, + 0.52845 + ], + [ + 0.299215, + 0.493024 + ] + ], + [ + [ + 0.319359, + 0.585575 + ], + [ + 0.353512, + 0.586429 + ] + ], + [ + [ + 0.36295, + 0.640103 + ], + [ + 0.397369, + 0.629461 + ] + ], + [ + [ + 0.358199, + 0.795594 + ], + [ + 0.397325, + 0.786112 + ] + ], + [ + [ + 0.38911, + 0.835102 + ], + [ + 0.358199, + 0.795594 + ] + ], + [ + [ + 0.5, + 0.915521 + ], + [ + 0.539816, + 0.897681 + ] + ], + [ + [ + 0.625933, + 0.889117 + ], + [ + 0.661289, + 0.855978 + ] + ], + [ + [ + 0.781626, + 0.799674 + ], + [ + 0.789979, + 0.783289 + ] + ], + [ + [ + 0.719048, + 0.79515 + ], + [ + 0.69557, + 0.834032 + ] + ], + [ + [ + 0.788177, + 0.731302 + ], + [ + 0.769491, + 0.766143 + ] + ], + [ + [ + 0.851589, + 0.654493 + ], + [ + 0.835695, + 0.70142 + ] + ], + [ + [ + 0.61089, + 0.835102 + ], + [ + 0.576854, + 0.869976 + ] + ], + [ + [ + 0.802679, + 0.666673 + ], + [ + 0.776457, + 0.657377 + ] + ], + [ + [ + 0.467944, + 0.753948 + ], + [ + 0.433765, + 0.772121 + ] + ], + [ + [ + 0.5, + 0.729889 + ], + [ + 0.467944, + 0.753948 + ] + ], + [ + [ + 0.636738, + 0.77242 + ], + [ + 0.599227, + 0.762925 + ] + ], + [ + [ + 0.632036, + 0.750203 + ], + [ + 0.595662, + 0.739531 + ] + ], + [ + [ + 0.402905, + 0.606403 + ], + [ + 0.383952, + 0.578959 + ] + ], + [ + [ + 0.388145, + 0.560448 + ], + [ + 0.375573, + 0.54265 + ] + ], + [ + [ + 0.385462, + 0.523682 + ], + [ + 0.360366, + 0.512298 + ] + ], + [ + [ + 0.371697, + 0.484507 + ], + [ + 0.340904, + 0.471542 + ] + ], + [ + [ + 0.382584, + 0.431999 + ], + [ + 0.405569, + 0.400545 + ] + ], + [ + [ + 0.417758, + 0.47177 + ], + [ + 0.440428, + 0.445551 + ] + ], + [ + [ + 0.440428, + 0.445551 + ], + [ + 0.468378, + 0.418915 + ] + ], + [ + [ + 0.464389, + 0.282468 + ], + [ + 0.429666, + 0.255524 + ] + ], + [ + [ + 0.500002, + 0.253129 + ], + [ + 0.535145, + 0.226154 + ] + ], + [ + [ + 0.569123, + 0.199884 + ], + [ + 0.534072, + 0.176336 + ] + ], + [ + [ + 0.53533, + 0.310525 + ], + [ + 0.570434, + 0.28432 + ] + ], + [ + [ + 0.636652, + 0.231764 + ], + [ + 0.603242, + 0.20184 + ] + ], + [ + [ + 0.699653, + 0.213363 + ], + [ + 0.669037, + 0.185453 + ] + ], + [ + [ + 0.603172, + 0.316418 + ], + [ + 0.635956, + 0.291522 + ] + ], + [ + [ + 0.745893, + 0.222378 + ], + [ + 0.722614, + 0.193397 + ] + ], + [ + [ + 0.761894, + 0.200631 + ], + [ + 0.739918, + 0.171847 + ] + ], + [ + [ + 0.754186, + 0.149596 + ], + [ + 0.774744, + 0.175659 + ] + ], + [ + [ + 0.78091, + 0.32009 + ], + [ + 0.814741, + 0.353575 + ] + ], + [ + [ + 0.78091, + 0.32009 + ], + [ + 0.767013, + 0.359819 + ] + ], + [ + [ + 0.633207, + 0.350878 + ], + [ + 0.666465, + 0.32811 + ] + ], + [ + [ + 0.666465, + 0.32811 + ], + [ + 0.691812, + 0.366345 + ] + ], + [ + [ + 0.732922, + 0.417156 + ], + [ + 0.744064, + 0.458173 + ] + ], + [ + [ + 0.796725, + 0.472363 + ], + [ + 0.801079, + 0.518188 + ] + ], + [ + [ + 0.628745, + 0.379294 + ], + [ + 0.65102, + 0.413728 + ] + ], + [ + [ + 0.564503, + 0.396171 + ], + [ + 0.5905, + 0.42614 + ] + ], + [ + [ + 0.691583, + 0.457998 + ], + [ + 0.677435, + 0.422433 + ] + ], + [ + [ + 0.721052, + 0.543894 + ], + [ + 0.752411, + 0.539539 + ] + ], + [ + [ + 0.643655, + 0.439287 + ], + [ + 0.659096, + 0.471542 + ] + ], + [ + [ + 0.842145, + 0.556834 + ], + [ + 0.867298, + 0.574218 + ] + ], + [ + [ + 0.586486, + 0.449947 + ], + [ + 0.606099, + 0.477574 + ] + ], + [ + [ + 0.631376, + 0.530317 + ], + [ + 0.656892, + 0.522611 + ] + ], + [ + [ + 0.453173, + 0.539117 + ], + [ + 0.42697, + 0.516088 + ] + ], + [ + [ + 0.453921, + 0.564295 + ], + [ + 0.476379, + 0.591481 + ] + ], + [ + [ + 0.523133, + 0.565435 + ], + [ + 0.546827, + 0.539117 + ] + ], + [ + [ + 0.523621, + 0.591481 + ], + [ + 0.546079, + 0.564295 + ] + ], + [ + [ + 0.610888, + 0.542238 + ], + [ + 0.631376, + 0.530317 + ] + ], + [ + [ + 0.549409, + 0.614939 + ], + [ + 0.570025, + 0.587166 + ] + ], + [ + [ + 0.65862, + 0.712004 + ], + [ + 0.621639, + 0.702497 + ] + ], + [ + [ + 0.776457, + 0.657377 + ], + [ + 0.74799, + 0.650015 + ] + ], + [ + [ + 0.712242, + 0.647377 + ], + [ + 0.674065, + 0.645079 + ] + ], + [ + [ + 0.817071, + 0.618378 + ], + [ + 0.788413, + 0.611361 + ] + ], + [ + [ + 0.760008, + 0.60426 + ], + [ + 0.728126, + 0.601536 + ] + ], + [ + [ + 0.680641, + 0.585575 + ], + [ + 0.646488, + 0.586429 + ] + ], + [ + [ + 0.688353, + 0.549111 + ], + [ + 0.656613, + 0.554373 + ] + ], + [ + [ + 0.624427, + 0.54265 + ], + [ + 0.611855, + 0.560448 + ] + ], + [ + [ + 0.97196, + 0.57075 + ], + [ + 0.977738, + 0.538836 + ] + ], + [ + [ + 0.311082, + 0.13133 + ], + [ + 0.290352, + 0.117067 + ] + ], + [ + [ + 0.381057, + 0.095866 + ], + [ + 0.354674, + 0.081219 + ] + ], + [ + [ + 0.19397, + 0.319876 + ], + [ + 0.225922, + 0.284195 + ] + ], + [ + [ + 0.409502, + 0.095408 + ], + [ + 0.386701, + 0.076986 + ] + ], + [ + [ + 0.225922, + 0.284195 + ], + [ + 0.251801, + 0.249597 + ] + ], + [ + [ + 0.194198, + 0.401517 + ], + [ + 0.232987, + 0.359819 + ] + ], + [ + [ + 0.274137, + 0.276527 + ], + [ + 0.300177, + 0.240364 + ] + ], + [ + [ + 0.398491, + 0.161839 + ], + [ + 0.367694, + 0.147781 + ] + ], + [ + [ + 0.46708, + 0.132105 + ], + [ + 0.436, + 0.11491 + ] + ], + [ + [ + 0.499926, + 0.115745 + ], + [ + 0.468215, + 0.096088 + ] + ], + [ + [ + 0.49999, + 0.132218 + ], + [ + 0.532339, + 0.115348 + ] + ], + [ + [ + 0.599774, + 0.147417 + ], + [ + 0.628704, + 0.131389 + ] + ], + [ + [ + 0.628704, + 0.131389 + ], + [ + 0.663923, + 0.148051 + ] + ], + [ + [ + 0.660039, + 0.131229 + ], + [ + 0.692626, + 0.148343 + ] + ], + [ + [ + 0.396444, + 0.228945 + ], + [ + 0.430877, + 0.199884 + ] + ], + [ + [ + 0.396246, + 0.25777 + ], + [ + 0.430164, + 0.227124 + ] + ], + [ + [ + 0.236286, + 0.474678 + ], + [ + 0.245588, + 0.431405 + ] + ], + [ + [ + 0.231562, + 0.517391 + ], + [ + 0.236286, + 0.474678 + ] + ], + [ + [ + 0.10866, + 0.53052 + ], + [ + 0.116431, + 0.565813 + ] + ], + [ + [ + 0.087946, + 0.596559 + ], + [ + 0.10679, + 0.593635 + ] + ], + [ + [ + 0.07422, + 0.58772 + ], + [ + 0.082729, + 0.603469 + ] + ], + [ + [ + 0.191869, + 0.5908 + ], + [ + 0.221643, + 0.584325 + ] + ], + [ + [ + 0.185215, + 0.758109 + ], + [ + 0.17209, + 0.722083 + ] + ], + [ + [ + 0.17209, + 0.722083 + ], + [ + 0.19011, + 0.716656 + ] + ], + [ + [ + 0.192747, + 0.692467 + ], + [ + 0.218503, + 0.683718 + ] + ], + [ + [ + 0.327486, + 0.881391 + ], + [ + 0.29747, + 0.847472 + ] + ], + [ + [ + 0.29747, + 0.847472 + ], + [ + 0.274747, + 0.813019 + ] + ], + [ + [ + 0.280952, + 0.79515 + ], + [ + 0.258947, + 0.758701 + ] + ], + [ + [ + 0.286625, + 0.775122 + ], + [ + 0.263394, + 0.738336 + ] + ], + [ + [ + 0.267983, + 0.717423 + ], + [ + 0.245788, + 0.675253 + ] + ], + [ + [ + 0.297712, + 0.736023 + ], + [ + 0.273127, + 0.695265 + ] + ], + [ + [ + 0.297426, + 0.623861 + ], + [ + 0.260958, + 0.624847 + ] + ], + [ + [ + 0.344172, + 0.604279 + ], + [ + 0.308015, + 0.603525 + ] + ], + [ + [ + 0.278948, + 0.543894 + ], + [ + 0.247589, + 0.539539 + ] + ], + [ + [ + 0.298501, + 0.432598 + ], + [ + 0.314559, + 0.395258 + ] + ], + [ + [ + 0.319338, + 0.481434 + ], + [ + 0.331413, + 0.447966 + ] + ], + [ + [ + 0.362191, + 0.569945 + ], + [ + 0.331293, + 0.569215 + ] + ], + [ + [ + 0.41782, + 0.66202 + ], + [ + 0.397369, + 0.629461 + ] + ], + [ + [ + 0.372823, + 0.727407 + ], + [ + 0.408457, + 0.71415 + ] + ], + [ + [ + 0.62948, + 0.901265 + ], + [ + 0.667153, + 0.869627 + ] + ], + [ + [ + 0.732348, + 0.826012 + ], + [ + 0.750978, + 0.796855 + ] + ], + [ + [ + 0.789979, + 0.783289 + ], + [ + 0.773103, + 0.783692 + ] + ], + [ + [ + 0.790911, + 0.753406 + ], + [ + 0.773103, + 0.783692 + ] + ], + [ + [ + 0.681978, + 0.796554 + ], + [ + 0.654419, + 0.838555 + ] + ], + [ + [ + 0.538207, + 0.87747 + ], + [ + 0.5, + 0.900318 + ] + ], + [ + [ + 0.781497, + 0.683718 + ], + [ + 0.754212, + 0.675253 + ] + ], + [ + [ + 0.641801, + 0.795594 + ], + [ + 0.61089, + 0.835102 + ] + ], + [ + [ + 0.535237, + 0.827433 + ], + [ + 0.5, + 0.803042 + ] + ], + [ + [ + 0.433765, + 0.772121 + ], + [ + 0.397325, + 0.786112 + ] + ], + [ + [ + 0.534165, + 0.801348 + ], + [ + 0.5, + 0.779463 + ] + ], + [ + [ + 0.606443, + 0.810872 + ], + [ + 0.568375, + 0.795845 + ] + ], + [ + [ + 0.599227, + 0.762925 + ], + [ + 0.563986, + 0.748378 + ] + ], + [ + [ + 0.470582, + 0.700434 + ], + [ + 0.438624, + 0.722601 + ] + ], + [ + [ + 0.595662, + 0.739531 + ], + [ + 0.561376, + 0.722601 + ] + ], + [ + [ + 0.4735, + 0.644005 + ], + [ + 0.450591, + 0.614939 + ] + ], + [ + [ + 0.375573, + 0.54265 + ], + [ + 0.355215, + 0.540977 + ] + ], + [ + [ + 0.379043, + 0.504525 + ], + [ + 0.350841, + 0.492846 + ] + ], + [ + [ + 0.388243, + 0.455771 + ], + [ + 0.4095, + 0.42614 + ] + ], + [ + [ + 0.430044, + 0.312753 + ], + [ + 0.396321, + 0.287274 + ] + ], + [ + [ + 0.467284, + 0.393467 + ], + [ + 0.5, + 0.365493 + ] + ], + [ + [ + 0.464484, + 0.254117 + ], + [ + 0.430164, + 0.227124 + ] + ], + [ + [ + 0.5, + 0.30977 + ], + [ + 0.464389, + 0.282468 + ] + ], + [ + [ + 0.500003, + 0.224998 + ], + [ + 0.534601, + 0.199264 + ] + ], + [ + [ + 0.603242, + 0.20184 + ], + [ + 0.568326, + 0.176723 + ] + ], + [ + [ + 0.699823, + 0.240364 + ], + [ + 0.669692, + 0.208874 + ] + ], + [ + [ + 0.743174, + 0.197208 + ], + [ + 0.71978, + 0.169805 + ] + ], + [ + [ + 0.757876, + 0.17378 + ], + [ + 0.736523, + 0.149231 + ] + ], + [ + [ + 0.751388, + 0.132196 + ], + [ + 0.770624, + 0.149916 + ] + ], + [ + [ + 0.699319, + 0.271195 + ], + [ + 0.724924, + 0.309956 + ] + ], + [ + [ + 0.724924, + 0.309956 + ], + [ + 0.74463, + 0.351726 + ] + ], + [ + [ + 0.566847, + 0.368986 + ], + [ + 0.601624, + 0.345134 + ] + ], + [ + [ + 0.738937, + 0.385941 + ], + [ + 0.754412, + 0.431405 + ] + ], + [ + [ + 0.710169, + 0.405275 + ], + [ + 0.723584, + 0.44444 + ] + ], + [ + [ + 0.532716, + 0.393467 + ], + [ + 0.562153, + 0.421761 + ] + ], + [ + [ + 0.750892, + 0.499072 + ], + [ + 0.744064, + 0.458173 + ] + ], + [ + [ + 0.668587, + 0.447966 + ], + [ + 0.680662, + 0.481434 + ] + ], + [ + [ + 0.628303, + 0.484507 + ], + [ + 0.659096, + 0.471542 + ] + ], + [ + [ + 0.606099, + 0.477574 + ], + [ + 0.620957, + 0.504525 + ] + ], + [ + [ + 0.600466, + 0.498459 + ], + [ + 0.614537, + 0.523682 + ] + ], + [ + [ + 0.52356, + 0.539125 + ], + [ + 0.549554, + 0.513681 + ] + ], + [ + [ + 0.47644, + 0.539125 + ], + [ + 0.5, + 0.566028 + ] + ], + [ + [ + 0.569509, + 0.539621 + ], + [ + 0.594884, + 0.519472 + ] + ], + [ + [ + 0.5, + 0.618421 + ], + [ + 0.476379, + 0.591481 + ] + ], + [ + [ + 0.527997, + 0.672063 + ], + [ + 0.552486, + 0.641045 + ] + ], + [ + [ + 0.558523, + 0.695596 + ], + [ + 0.527997, + 0.672063 + ] + ], + [ + [ + 0.555542, + 0.668171 + ], + [ + 0.577446, + 0.636535 + ] + ], + [ + [ + 0.587003, + 0.68793 + ], + [ + 0.555542, + 0.668171 + ] + ], + [ + [ + 0.664577, + 0.734115 + ], + [ + 0.689795, + 0.693457 + ] + ], + [ + [ + 0.644806, + 0.662831 + ], + [ + 0.60899, + 0.652901 + ] + ], + [ + [ + 0.728126, + 0.601536 + ], + [ + 0.691985, + 0.603525 + ] + ], + [ + [ + 0.99504, + 0.539595 + ], + [ + 0.977738, + 0.538836 + ] + ] + ] +} \ No newline at end of file diff --git a/editor/body_mesh/__init__.py b/editor/body_mesh/__init__.py index 1c6864b..5db0fde 100644 --- a/editor/body_mesh/__init__.py +++ b/editor/body_mesh/__init__.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later -from .ops import VRM_OT_separate_non_skin_faces +from .ops import VRM_OT_merge_uv_edges, VRM_OT_separate_non_skin_faces from .panel import VRM_PT_body_mesh_separator __all__ = [ + "VRM_OT_merge_uv_edges", "VRM_OT_separate_non_skin_faces", "VRM_PT_body_mesh_separator", ] diff --git a/editor/body_mesh/__pycache__/utils.cpython-313.pyc b/editor/body_mesh/__pycache__/utils.cpython-313.pyc index b4c3d1a..cfbf48a 100644 Binary files a/editor/body_mesh/__pycache__/utils.cpython-313.pyc and b/editor/body_mesh/__pycache__/utils.cpython-313.pyc differ diff --git a/editor/body_mesh/ops.py b/editor/body_mesh/ops.py index a18a405..17e1487 100644 --- a/editor/body_mesh/ops.py +++ b/editor/body_mesh/ops.py @@ -1,28 +1,58 @@ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later from collections.abc import Set as AbstractSet +from dataclasses import dataclass from typing import TYPE_CHECKING, Optional import bpy from bpy.types import Context, Operator from .utils import ( + AUTO_CONVERT_MATERIALS, + PROCESSING_ORDER, MaterialInfo, find_body_mesh, + find_face_mesh, generate_unique_name, get_material_face_count, + get_target_mesh_name, + is_retained_material, + is_separable_material, is_skin_material, + isolate_material_faces, + load_edge_uv_data, + select_edges_by_uv, ) if TYPE_CHECKING: from bpy.types import Object +@dataclass +class ProcessResult: + """材质处理结果""" + material_name: str + mesh_name: str + edges_selected: int + edges_dissolved: int + success: bool + error_message: Optional[str] = None + + +@dataclass +class MergeOperationState: + """操作状态,用于错误恢复""" + original_mode: str + original_active_object: Optional["Object"] + original_selected_objects: list["Object"] + processed_materials: list[str] + + class VRM_OT_separate_non_skin_faces(Operator): """将Body网格中非皮肤材质的面分离为独立对象""" bl_idname = "vrm.separate_non_skin_faces" bl_label = "Separate Non-Skin Faces" - bl_description = "将Body网格中非皮肤材质的面分离为独立对象" + bl_description = "将Body网格中可分离材质(CLOTH和其他)的面合并分离为单个独立对象" bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} @classmethod @@ -36,13 +66,13 @@ class VRM_OT_separate_non_skin_faces(Operator): if body_mesh is None: return False - # 检查是否有非皮肤材质 + # 检查是否有可分离材质(使用is_separable_material) mesh_data = body_mesh.data if mesh_data is None: return False for material in mesh_data.materials: - if material is not None and not is_skin_material(material.name): + if material is not None and is_separable_material(material.name): return True return False @@ -88,13 +118,13 @@ class VRM_OT_separate_non_skin_faces(Operator): self.report({"ERROR"}, "Body网格数据无效") return {"CANCELLED"} - # 收集非皮肤材质信息 - non_skin_materials: list[MaterialInfo] = [] + # 收集可分离材质信息(使用is_separable_material) + separable_materials: list[MaterialInfo] = [] for i, material in enumerate(mesh_data.materials): - if material is not None and not is_skin_material(material.name): + if material is not None and is_separable_material(material.name): face_count = get_material_face_count(mesh_data, i) if face_count > 0: - non_skin_materials.append( + separable_materials.append( MaterialInfo( name=material.name, index=i, @@ -103,39 +133,40 @@ class VRM_OT_separate_non_skin_faces(Operator): ) ) - if not non_skin_materials: - self.report({"INFO"}, "没有找到非皮肤材质的面") + if not separable_materials: + self.report({"INFO"}, "没有找到可分离材质的面") return {"FINISHED"} # 获取现有对象名称集合 existing_names = set(obj.name for obj in context.blend_data.objects) - # 为每个非皮肤材质执行分离 - separated_count = 0 - for mat_info in non_skin_materials: - result = self._separate_material_faces( - context, body_mesh, armature, mat_info, existing_names - ) - if result: - separated_count += 1 - existing_names.add(result.name) + # 将所有可分离材质的面合并分离为单个对象 + material_indices = [mat_info.index for mat_info in separable_materials] + new_obj = self._separate_all_material_faces( + context, body_mesh, armature, material_indices, existing_names + ) - if separated_count > 0: - self.report({"INFO"}, f"成功分离了 {separated_count} 个材质的面") + if new_obj is not None: + total_faces = sum(mat_info.face_count for mat_info in separable_materials) + material_names = [mat_info.name for mat_info in separable_materials] + self.report( + {"INFO"}, + f"成功分离了 {len(separable_materials)} 个材质的 {total_faces} 个面到 {new_obj.name}" + ) return {"FINISHED"} else: self.report({"WARNING"}, "没有成功分离任何面") return {"CANCELLED"} - def _separate_material_faces( + def _separate_all_material_faces( self, context: Context, body_obj: "Object", armature: "Object", - mat_info: MaterialInfo, + material_indices: list[int], existing_names: set[str], ) -> Optional["Object"]: - """分离指定材质的面为新对象""" + """将所有指定材质的面合并分离为单个新对象""" mesh_data = body_obj.data if mesh_data is None: return None @@ -163,9 +194,10 @@ class VRM_OT_separate_non_skin_faces(Operator): # 返回对象模式以访问网格数据 bpy.ops.object.mode_set(mode="OBJECT") - # 选择使用指定材质的面 + # 选择所有使用可分离材质的面 + material_index_set = set(material_indices) for polygon in mesh_data.polygons: - if polygon.material_index == mat_info.index: + if polygon.material_index in material_index_set: polygon.select = True # 返回编辑模式执行分离 @@ -182,8 +214,8 @@ class VRM_OT_separate_non_skin_faces(Operator): if new_obj is None: return None - # 生成唯一名称 - base_name = self._generate_object_name(mat_info.name) + # 使用固定名称"Body_Separated",处理命名冲突 + base_name = "Body_Separated" unique_name = generate_unique_name(base_name, existing_names) new_obj.name = unique_name @@ -203,18 +235,405 @@ class VRM_OT_separate_non_skin_faces(Operator): return obj return None - def _generate_object_name(self, material_name: str) -> str: - """根据材质名称生成对象名称""" - # 简化材质名称,移除常见前缀和后缀 - simplified_name = material_name - # 移除 "(Instance)" 后缀 - if simplified_name.endswith(" (Instance)"): - simplified_name = simplified_name[:-11] - # 移除常见的VRM材质前缀 - prefixes_to_remove = ["N00_000_00_", "N00_001_00_", "N00_002_00_"] - for prefix in prefixes_to_remove: - if simplified_name.startswith(prefix): - simplified_name = simplified_name[len(prefix) :] - break - return f"Body_{simplified_name}" +class VRM_OT_merge_uv_edges(Operator): + """根据UV坐标融并边,将三角面转四边面""" + + bl_idname = "vrm.merge_uv_edges" + bl_label = "Merge UV Edges" + bl_description = "根据UV坐标选中并融并边,将三角面转换为四边面" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context: Context) -> bool: + """ + 检查是否可以执行操作 + + Requirements 1.1, 1.2, 1.3, 1.4: + - 检测Armature对象或其子网格对象 + - 检测Body和Face网格是否存在 + - 在任意Blender模式下工作 + """ + armature = cls._get_armature(context) + if armature is None: + return False + + # Requirements 1.1, 1.2: 检查Body和Face网格是否都存在 + body_mesh = find_body_mesh(armature) + face_mesh = find_face_mesh(armature) + + return body_mesh is not None and face_mesh is not None + + @classmethod + def _get_armature(cls, context: Context) -> Optional["Object"]: + """ + 获取当前选中的Armature对象 + + Requirements 1.1: 支持选中Armature或其子网格对象 + """ + active_object = context.active_object + if active_object is None: + return None + + if active_object.type == "ARMATURE": + return active_object + + # 如果选中的是网格,检查其父对象是否为Armature + if active_object.parent is not None and active_object.parent.type == "ARMATURE": + return active_object.parent + + return None + + def execute(self, context: Context) -> set[str]: + """ + 执行边融并操作 + + Requirements 5.3, 5.4, 6.1, 6.2: + - 保存原始状态(模式、选择、活动对象) + - 按PROCESSING_ORDER遍历材质 + - 调用_process_material处理每个材质 + - 恢复状态并报告结果 + """ + # 保存原始状态 + state = self._save_state(context) + + try: + return self._execute_merge(context, state) + except Exception as e: + # 发生错误时恢复状态 + self._restore_state(context, state) + self.report({"ERROR"}, f"边融并操作失败: {str(e)}") + return {"CANCELLED"} + + def _save_state(self, context: Context) -> MergeOperationState: + """保存当前Blender状态""" + original_mode = context.object.mode if context.object else "OBJECT" + original_active = context.view_layer.objects.active + original_selected = list(context.selected_objects) + + return MergeOperationState( + original_mode=original_mode, + original_active_object=original_active, + original_selected_objects=original_selected, + processed_materials=[], + ) + + def _restore_state(self, context: Context, state: MergeOperationState) -> None: + """ + 恢复原始Blender状态 + + Requirements 7.4: 错误时恢复原始状态 + """ + try: + # 确保在对象模式 + if context.object and context.object.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + # 取消所有选择 + bpy.ops.object.select_all(action="DESELECT") + + # 恢复原始选择 + for obj in state.original_selected_objects: + if obj and obj.name in context.blend_data.objects: + obj.select_set(True) + + # 恢复原始活动对象 + if state.original_active_object and \ + state.original_active_object.name in context.blend_data.objects: + context.view_layer.objects.active = state.original_active_object + + # 恢复原始模式 + if context.object and state.original_mode != "OBJECT": + try: + bpy.ops.object.mode_set(mode=state.original_mode) + except RuntimeError: + pass # 某些模式可能无法恢复 + except Exception: + pass # 恢复失败时静默处理 + + def _execute_merge( + self, context: Context, state: MergeOperationState + ) -> set[str]: + """ + 执行边融并的核心逻辑 + + Requirements 6.1, 6.2: 按顺序处理材质,先Body后Face + """ + armature = self._get_armature(context) + if armature is None: + self.report({"ERROR"}, "未找到Armature对象") + return {"CANCELLED"} + + body_mesh = find_body_mesh(armature) + face_mesh = find_face_mesh(armature) + + if body_mesh is None or face_mesh is None: + self.report({"ERROR"}, "未找到Body或Face网格对象") + return {"CANCELLED"} + + # 确保在对象模式开始 + if context.object and context.object.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + # 处理结果统计 + results: list[ProcessResult] = [] + total_edges_dissolved = 0 + materials_processed = 0 + materials_skipped = 0 + + # 按PROCESSING_ORDER遍历材质 (Requirements 6.1, 6.2) + for material_name in PROCESSING_ORDER: + # 确定目标网格 + target_mesh_name = get_target_mesh_name(material_name) + if target_mesh_name is None: + continue + + target_mesh = body_mesh if target_mesh_name == "Body" else face_mesh + + # 处理材质 + result = self._process_material(context, target_mesh, material_name) + results.append(result) + state.processed_materials.append(material_name) + + if result.success: + materials_processed += 1 + total_edges_dissolved += result.edges_dissolved + else: + materials_skipped += 1 + if result.error_message: + self.report({"WARNING"}, result.error_message) + + # 确保返回对象模式 (Requirements 5.4) + if context.object and context.object.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + # 报告结果 (Requirements 6.4) + if materials_processed > 0: + self.report( + {"INFO"}, + f"处理完成: {materials_processed} 个材质, " + f"融并 {total_edges_dissolved} 条边" + ) + return {"FINISHED"} + else: + self.report({"WARNING"}, f"没有成功处理任何材质 (跳过 {materials_skipped} 个)") + return {"CANCELLED"} + + def _process_material( + self, + context: Context, + mesh_obj: "Object", + material_name: str, + ) -> ProcessResult: + """ + 处理单个材质的边融并 + + Requirements 5.1, 5.2, 6.3, 7.1, 7.2: + - 确定目标网格(Body或Face) + - 加载JSON边数据 + - 调用isolate_material_faces隔离材质 + - 调用select_edges_by_uv选择边 + - 执行dissolve_edges融并 + - 取消隐藏(Alt+H) + - 处理缺失JSON文件 (7.1) + - 处理无匹配边情况 (7.2) + """ + import bmesh + + mesh_name = mesh_obj.name if mesh_obj else "Unknown" + + # 检查是否使用自动转换(tris_convert_to_quads) + if material_name in AUTO_CONVERT_MATERIALS: + return self._process_material_auto_convert(context, mesh_obj, material_name) + + try: + # 加载JSON边数据 (Requirements 5.1, 7.1) + target_uvs = load_edge_uv_data(material_name) + if target_uvs is None: + # Requirements 7.1: 记录警告并跳过 + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"跳过: 未找到材质 {material_name} 的JSON文件", + ) + + if len(target_uvs) == 0: + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"跳过: 材质 {material_name} 的JSON文件中没有边数据", + ) + + # 隔离材质面 (Requirements 5.2) + if not isolate_material_faces(context, mesh_obj, material_name): + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"跳过: 无法隔离材质 {material_name} 的面", + ) + + # 此时应该在编辑模式,边选择模式 + # 取消所有边选择 + bpy.ops.mesh.select_all(action="DESELECT") + + # 使用BMesh选择边 + mesh_data = mesh_obj.data + bm = bmesh.from_edit_mesh(mesh_data) + + # 获取UV层 + uv_layer = bm.loops.layers.uv.active + if uv_layer is None: + # 取消隐藏并返回 + bpy.ops.mesh.reveal() + bpy.ops.object.mode_set(mode="OBJECT") + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"跳过: 网格 {mesh_name} 没有UV层", + ) + + # 选择匹配UV的边 (Requirements 5.2) + edges_selected = select_edges_by_uv(bm, uv_layer, target_uvs) + + # 更新BMesh + bmesh.update_edit_mesh(mesh_data) + + # Requirements 7.2: 处理无匹配边情况 + if edges_selected == 0: + # 取消隐藏并返回 + bpy.ops.mesh.reveal() + bpy.ops.object.mode_set(mode="OBJECT") + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=( + f"跳过: 材质 {material_name} 没有找到匹配的边 " + f"(目标边数: {len(target_uvs)})" + ), + ) + + # 执行边融并 (Requirements 5.1) + edges_dissolved = 0 + try: + bpy.ops.mesh.dissolve_edges() + edges_dissolved = edges_selected + except RuntimeError as e: + # 融并失败,可能是边不可融并,但不算完全失败 + self.report({"WARNING"}, f"材质 {material_name} 融并边时出现问题: {str(e)}") + + # 取消隐藏 (Alt+H equivalent) (Requirements 5.2) + bpy.ops.mesh.reveal() + + # 返回对象模式 (Requirements 6.3) + bpy.ops.object.mode_set(mode="OBJECT") + + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=edges_selected, + edges_dissolved=edges_dissolved, + success=True, + error_message=None, + ) + + except Exception as e: + # Requirements 7.4: 捕获异常,尝试恢复状态 + try: + # 尝试取消隐藏 + if context.object and context.object.mode == "EDIT": + bpy.ops.mesh.reveal() + bpy.ops.object.mode_set(mode="OBJECT") + except Exception: + pass + + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"处理材质 {material_name} 时发生错误: {str(e)}", + ) + + def _process_material_auto_convert( + self, + context: Context, + mesh_obj: "Object", + material_name: str, + ) -> ProcessResult: + """ + 使用Blender自带的tris_convert_to_quads处理规律网格的材质 + + 对于网格结构规律的材质,直接使用Blender的自动转换功能, + 不需要基于UV坐标的边选择。 + """ + mesh_name = mesh_obj.name if mesh_obj else "Unknown" + + try: + # 隔离材质面 + if not isolate_material_faces(context, mesh_obj, material_name): + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"跳过: 无法隔离材质 {material_name} 的面", + ) + + # 切换到面选择模式 + bpy.ops.mesh.select_mode(type="FACE") + + # 选择所有可见的面 + bpy.ops.mesh.select_all(action="SELECT") + + # 使用Blender自带的三角面转四边面 + bpy.ops.mesh.tris_convert_to_quads() + + # 取消隐藏 + bpy.ops.mesh.reveal() + + # 返回对象模式 + bpy.ops.object.mode_set(mode="OBJECT") + + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=True, + error_message=None, + ) + + except Exception as e: + # 捕获异常,尝试恢复状态 + try: + if context.object and context.object.mode == "EDIT": + bpy.ops.mesh.reveal() + bpy.ops.object.mode_set(mode="OBJECT") + except Exception: + pass + + return ProcessResult( + material_name=material_name, + mesh_name=mesh_name, + edges_selected=0, + edges_dissolved=0, + success=False, + error_message=f"处理材质 {material_name} 时发生错误: {str(e)}", + ) diff --git a/editor/body_mesh/panel.py b/editor/body_mesh/panel.py index 4a8a47a..b3df756 100644 --- a/editor/body_mesh/panel.py +++ b/editor/body_mesh/panel.py @@ -7,8 +7,9 @@ from bpy.types import Context, Panel from .utils import ( MaterialInfo, find_body_mesh, + find_face_mesh, get_material_face_count, - is_skin_material, + is_retained_material, ) if TYPE_CHECKING: @@ -69,45 +70,64 @@ class VRM_PT_body_mesh_separator(Panel): layout.label(text="Body网格数据无效", icon="ERROR") return - # 显示材质统计信息 + # 显示材质统计信息(区分保留材质和可分离材质) box = layout.box() box.label(text="材质列表", icon="MATERIAL") - # 收集材质信息 + # 收集材质信息,使用is_retained_material和is_separable_material分类 materials_info: list[MaterialInfo] = [] for i, material in enumerate(mesh_data.materials): if material is not None: face_count = get_material_face_count(mesh_data, i) - is_skin = is_skin_material(material.name) + is_retained = is_retained_material(material.name) materials_info.append( MaterialInfo( name=material.name, index=i, face_count=face_count, - is_skin=is_skin, + is_skin=is_retained, # 使用is_skin字段存储保留状态 ) ) - # 显示材质列表 - has_non_skin = False + # 显示材质列表,区分保留材质(SKIN/HAIR)和可分离材质(CLOTH和其他) + has_separable = False for mat_info in materials_info: row = box.row() - if mat_info.is_skin: - row.label(text=f"[皮肤] {mat_info.name}", icon="CHECKMARK") + if mat_info.is_skin: # is_skin字段现在表示是否为保留材质 + # 保留材质(SKIN/HAIR)标记为"[保留]" + row.label(text=f"[保留] {mat_info.name}", icon="CHECKMARK") else: - row.label(text=f"[非皮肤] {mat_info.name}", icon="MESH_DATA") - has_non_skin = True + # 可分离材质(CLOTH和其他)标记为"[分离]" + row.label(text=f"[分离] {mat_info.name}", icon="MESH_DATA") + has_separable = True row.label(text=f"{mat_info.face_count} 面") # 显示分离操作按钮 layout.separator() col = layout.column() - col.enabled = has_non_skin + col.enabled = has_separable col.operator( "vrm.separate_non_skin_faces", text="分离非皮肤面", icon="MESH_DATA", ) - if not has_non_skin: - layout.label(text="没有非皮肤材质可分离", icon="INFO") + if not has_separable: + layout.label(text="没有可分离材质", icon="INFO") + + # 显示UV边融并按钮 (Requirements 1.2, 1.3) + layout.separator() + armature = self._get_armature(context) + face_mesh = find_face_mesh(armature) if armature else None + has_vrm_structure = body_mesh is not None and face_mesh is not None + + col = layout.column() + col.enabled = has_vrm_structure + col.operator( + "vrm.merge_uv_edges", + text="根据UV转四边面", + icon="MOD_EDGESPLIT", + ) + + if not has_vrm_structure: + layout.label(text="需要Body和Face网格", icon="INFO") diff --git a/editor/body_mesh/test_utils.py b/editor/body_mesh/test_utils.py index 684db20..412cd12 100644 --- a/editor/body_mesh/test_utils.py +++ b/editor/body_mesh/test_utils.py @@ -3,11 +3,19 @@ Property-based tests for body_mesh utility functions. Feature: vrm-body-mesh-separator +Feature: vrm-uv-edge-merger """ from hypothesis import given, settings from hypothesis import strategies as st -from .utils import generate_unique_name, is_skin_material +from .utils import ( + find_body_mesh, + find_face_mesh, + generate_unique_name, + is_skin_material, + normalize_uv_pair, + select_material_faces, +) class TestIsSkinMaterial: @@ -58,3 +66,431 @@ class TestGenerateUniqueName: f"generate_unique_name('{base_name}', {existing_names}) " f"returned '{result}' which is in existing_names" ) + + + +class TestNormalizeUvPair: + """ + Property 4: UV坐标标准化对称性 + *For any* pair of UV coordinates (uv1, uv2), normalizing (uv1, uv2) SHALL produce + the same result as normalizing (uv2, uv1), ensuring order-independent matching. + **Validates: Requirements 4.5** + """ + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + ) + @settings(max_examples=100) + def test_uv_normalization_symmetry_property( + self, uv1: tuple[float, float], uv2: tuple[float, float] + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 4: UV Coordinate Normalization + For any pair of UV coordinates, normalize(uv1, uv2) == normalize(uv2, uv1). + """ + result_forward = normalize_uv_pair(uv1, uv2) + result_reverse = normalize_uv_pair(uv2, uv1) + assert result_forward == result_reverse, ( + f"normalize_uv_pair({uv1}, {uv2}) = {result_forward}, " + f"but normalize_uv_pair({uv2}, {uv1}) = {result_reverse}" + ) + + +class TestUvEdgeMatchingPrecision: + """ + Property 5: UV边匹配精度 + *For any* edge with UV coordinates, the matching function SHALL correctly identify + the edge if its UV coordinates (rounded to 6 decimal places) exist in the target UV set. + **Validates: Requirements 4.2, 4.3, 4.4** + """ + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.floats(min_value=-1e-7, max_value=1e-7, allow_nan=False, allow_infinity=False), + ) + @settings(max_examples=100) + def test_uv_matching_precision_property( + self, + uv1: tuple[float, float], + uv2: tuple[float, float], + noise: float, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 5: UV Edge Matching Precision + For any UV coordinate pair, adding noise smaller than 6 decimal precision + should still result in a match after normalization. + """ + # Create the normalized reference UV pair + normalized_ref = normalize_uv_pair(uv1, uv2) + + # Add small noise (within 6 decimal precision tolerance) + uv1_noisy = (uv1[0] + noise, uv1[1] + noise) + uv2_noisy = (uv2[0] + noise, uv2[1] + noise) + + # Normalize the noisy pair + normalized_noisy = normalize_uv_pair(uv1_noisy, uv2_noisy) + + # Create target UV set with the reference + target_uvs = {normalized_ref} + + # The noisy normalized pair should match the reference + # because the noise is within 6 decimal precision + matches = normalized_noisy in target_uvs + + # Calculate expected match based on rounding behavior + # If noise is small enough, rounding should produce same result + expected_match = normalized_ref == normalized_noisy + + assert matches == expected_match, ( + f"UV matching inconsistency: " + f"normalized_ref={normalized_ref}, normalized_noisy={normalized_noisy}, " + f"noise={noise}, matches={matches}, expected={expected_match}" + ) + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + ) + @settings(max_examples=100) + def test_uv_matching_set_membership_property( + self, + uv1: tuple[float, float], + uv2: tuple[float, float], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 5: UV Edge Matching Precision + For any UV coordinate pair, the normalized pair should be found in a target + set containing that same normalized pair, regardless of original order. + """ + # Normalize in both orders + normalized_forward = normalize_uv_pair(uv1, uv2) + normalized_reverse = normalize_uv_pair(uv2, uv1) + + # Create target set with forward normalization + target_uvs = {normalized_forward} + + # Both should match (symmetry property ensures this) + assert normalized_forward in target_uvs, ( + f"Forward normalized pair {normalized_forward} not found in target set" + ) + assert normalized_reverse in target_uvs, ( + f"Reverse normalized pair {normalized_reverse} not found in target set " + f"(forward was {normalized_forward})" + ) + + +class MockPolygon: + """Mock polygon for testing material face selection.""" + + def __init__(self, material_index: int) -> None: + self.material_index = material_index + + +class MockMesh: + """Mock mesh data for testing material face selection.""" + + def __init__(self, polygons: list[MockPolygon]) -> None: + self.polygons = polygons + + +class MockMeshObject: + """Mock mesh object for testing material face selection.""" + + def __init__(self, mesh_data: MockMesh) -> None: + self.type = "MESH" + self.data = mesh_data + + +class TestMaterialFaceSelectionCompleteness: + """ + Property 6: Material Face Selection Completeness + *For any* mesh and material index, the face selection function SHALL select + exactly all faces that have the specified material index, and no others. + **Validates: Requirements 3.2** + """ + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=1, + max_size=50, + ), + st.integers(min_value=0, max_value=5), + ) + @settings(max_examples=100) + def test_material_face_selection_completeness_property( + self, + material_indices: list[int], + target_material_index: int, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh with faces having various material indices, select_material_faces + returns exactly the set of face indices that have the specified material index. + """ + # Create mock polygons with the given material indices + polygons = [MockPolygon(idx) for idx in material_indices] + mock_mesh = MockMesh(polygons) + mock_obj = MockMeshObject(mock_mesh) + + # Call the function under test + result = select_material_faces(mock_obj, target_material_index) + + # Calculate expected result: indices of faces with the target material + expected = { + i for i, mat_idx in enumerate(material_indices) + if mat_idx == target_material_index + } + + # Verify completeness: result should contain exactly the expected faces + assert result == expected, ( + f"select_material_faces returned {result}, expected {expected}. " + f"Material indices: {material_indices}, target: {target_material_index}" + ) + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=0, + max_size=50, + ), + ) + @settings(max_examples=100) + def test_material_face_selection_no_false_positives_property( + self, + material_indices: list[int], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh, selecting faces for a material index that doesn't exist + should return an empty set. + """ + # Create mock polygons + polygons = [MockPolygon(idx) for idx in material_indices] + mock_mesh = MockMesh(polygons) + mock_obj = MockMeshObject(mock_mesh) + + # Use a material index that definitely doesn't exist + non_existent_index = max(material_indices, default=-1) + 10 + + # Call the function under test + result = select_material_faces(mock_obj, non_existent_index) + + # Should return empty set for non-existent material + assert result == set(), ( + f"select_material_faces returned {result} for non-existent material " + f"index {non_existent_index}, expected empty set" + ) + + def test_material_face_selection_invalid_object(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + Selecting faces from None or non-mesh object should return empty set. + """ + # Test with None + result_none = select_material_faces(None, 0) + assert result_none == set(), "Expected empty set for None object" + + # Test with non-mesh object (mock with wrong type) + class NonMeshObject: + type = "ARMATURE" + data = None + + result_non_mesh = select_material_faces(NonMeshObject(), 0) + assert result_non_mesh == set(), "Expected empty set for non-mesh object" + + +class MockChild: + """Mock child object for testing VRM model detection.""" + + def __init__(self, name: str, obj_type: str = "MESH") -> None: + self.name = name + self.type = obj_type + + +class MockArmature: + """Mock armature object for testing VRM model detection.""" + + def __init__(self, children: list[MockChild]) -> None: + self.type = "ARMATURE" + self.children = children + + +class TestVRMModelDetectionCorrectness: + """ + Property 1: VRM Model Detection Correctness + *For any* Blender object hierarchy, the VRM detection function SHALL return True + if and only if the hierarchy contains an Armature with both "Body" and "Face" + mesh children, regardless of the current Blender mode. + **Validates: Requirements 1.1, 1.2, 1.3, 1.4** + """ + + @given( + st.lists( + st.tuples( + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + st.sampled_from(["MESH", "ARMATURE", "EMPTY", "LIGHT", "CAMERA"]), + ), + min_size=0, + max_size=10, + ), + ) + @settings(max_examples=100) + def test_vrm_detection_requires_both_body_and_face( + self, + children_specs: list[tuple[str, str]], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + For any set of child objects, VRM detection returns True if and only if + both "Body" and "Face" mesh children exist. + """ + # Create mock children + children = [MockChild(name, obj_type) for name, obj_type in children_specs] + mock_armature = MockArmature(children) + + # Check if Body and Face meshes exist + has_body_mesh = any( + c.name == "Body" and c.type == "MESH" for c in children + ) + has_face_mesh = any( + c.name == "Face" and c.type == "MESH" for c in children + ) + + # Test find_body_mesh + body_result = find_body_mesh(mock_armature) + if has_body_mesh: + assert body_result is not None, ( + f"find_body_mesh should find Body mesh in {children_specs}" + ) + assert body_result.name == "Body", ( + f"find_body_mesh returned wrong object: {body_result.name}" + ) + else: + assert body_result is None, ( + f"find_body_mesh should return None for {children_specs}" + ) + + # Test find_face_mesh + face_result = find_face_mesh(mock_armature) + if has_face_mesh: + assert face_result is not None, ( + f"find_face_mesh should find Face mesh in {children_specs}" + ) + assert face_result.name == "Face", ( + f"find_face_mesh returned wrong object: {face_result.name}" + ) + else: + assert face_result is None, ( + f"find_face_mesh should return None for {children_specs}" + ) + + # VRM detection should be True only if both exist + expected_vrm_valid = has_body_mesh and has_face_mesh + actual_vrm_valid = body_result is not None and face_result is not None + assert actual_vrm_valid == expected_vrm_valid, ( + f"VRM detection mismatch: expected {expected_vrm_valid}, " + f"got {actual_vrm_valid} for {children_specs}" + ) + + def test_vrm_detection_with_valid_vrm_structure(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + A valid VRM structure with both Body and Face meshes should be detected. + """ + children = [ + MockChild("Body", "MESH"), + MockChild("Face", "MESH"), + MockChild("Hair", "MESH"), + ] + mock_armature = MockArmature(children) + + body = find_body_mesh(mock_armature) + face = find_face_mesh(mock_armature) + + assert body is not None, "Body mesh should be found" + assert face is not None, "Face mesh should be found" + assert body.name == "Body" + assert face.name == "Face" + + def test_vrm_detection_with_missing_body(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Missing Body mesh should result in invalid VRM detection. + """ + children = [ + MockChild("Face", "MESH"), + MockChild("Hair", "MESH"), + ] + mock_armature = MockArmature(children) + + body = find_body_mesh(mock_armature) + face = find_face_mesh(mock_armature) + + assert body is None, "Body mesh should not be found" + assert face is not None, "Face mesh should be found" + + def test_vrm_detection_with_missing_face(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Missing Face mesh should result in invalid VRM detection. + """ + children = [ + MockChild("Body", "MESH"), + MockChild("Hair", "MESH"), + ] + mock_armature = MockArmature(children) + + body = find_body_mesh(mock_armature) + face = find_face_mesh(mock_armature) + + assert body is not None, "Body mesh should be found" + assert face is None, "Face mesh should not be found" + + def test_vrm_detection_with_non_mesh_body(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Body object that is not a MESH should not be detected as Body mesh. + """ + children = [ + MockChild("Body", "EMPTY"), # Not a mesh + MockChild("Face", "MESH"), + ] + mock_armature = MockArmature(children) + + body = find_body_mesh(mock_armature) + face = find_face_mesh(mock_armature) + + assert body is None, "Non-mesh Body should not be found" + assert face is not None, "Face mesh should be found" + + def test_vrm_detection_with_none_armature(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + None armature should return None for both meshes. + """ + body = find_body_mesh(None) + face = find_face_mesh(None) + + assert body is None, "Body should be None for None armature" + assert face is None, "Face should be None for None armature" diff --git a/editor/body_mesh/utils.py b/editor/body_mesh/utils.py index 437304f..9211d24 100644 --- a/editor/body_mesh/utils.py +++ b/editor/body_mesh/utils.py @@ -1,9 +1,52 @@ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later +import json +import os from dataclasses import dataclass from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from bpy.types import Mesh, Object + from bmesh.types import BMesh, BMLayerItem + from bpy.types import Context, Mesh, Object + + +# UV坐标精度(小数位数) +UV_PRECISION = 6 + + +# 材质到网格的映射 +MATERIAL_MESH_MAPPING: dict[str, str] = { + "N00_000_00_Body_00_SKIN (Instance)": "Body", + "N00_000_00_Face_00_SKIN (Instance)": "Face", + "N00_000_00_FaceMouth_00_FACE (Instance)": "Face", + "N00_000_00_FaceEyeline_00_FACE (Instance)": "Face", + "N00_000_00_FaceEyelash_00_FACE (Instance)": "Face", + "N00_000_00_FaceBrow_00_FACE (Instance)": "Face", + "N00_000_00_hH_00_FACE (Instance)": "Face", + "N00_000_00_EyeIris_00_EYE (Instance)": "Face", + "N00_000_00_EyeWhite_00_EYE (Instance)": "Face", + "N00_000_00_EyeHighlight_00_EYE (Instance)": "Face", + "N00_000_00_HairBack_00_HAIR (Instance)": "Body", +} + +# 使用Blender自带tris_convert_to_quads的材质(网格规律,不需要UV匹配) +AUTO_CONVERT_MATERIALS: set[str] = { + "N00_000_00_FaceBrow_00_FACE (Instance)", +} + +# 处理顺序(先Body后Face) +PROCESSING_ORDER: list[str] = [ + "N00_000_00_Body_00_SKIN (Instance)", + "N00_000_00_HairBack_00_HAIR (Instance)", + "N00_000_00_Face_00_SKIN (Instance)", + "N00_000_00_FaceMouth_00_FACE (Instance)", + "N00_000_00_FaceEyeline_00_FACE (Instance)", + "N00_000_00_FaceEyelash_00_FACE (Instance)", + "N00_000_00_FaceBrow_00_FACE (Instance)", + "N00_000_00_hH_00_FACE (Instance)", + "N00_000_00_EyeIris_00_EYE (Instance)", + "N00_000_00_EyeWhite_00_EYE (Instance)", + "N00_000_00_EyeHighlight_00_EYE (Instance)", +] @dataclass @@ -35,6 +78,64 @@ def find_body_mesh(armature: "Object") -> Optional["Object"]: return None +def find_face_mesh(armature: "Object") -> Optional["Object"]: + """ + 在Armature的子对象中查找名为"Face"的网格对象 + + Args: + armature: Armature对象 + + Returns: + Face网格对象,如果不存在则返回None + """ + if armature is None: + return None + + for child in armature.children: + if child.type == "MESH" and child.name == "Face": + return child + + return None + + +def get_target_mesh_name(material_name: str) -> Optional[str]: + """ + 根据材质名称获取目标网格名称 + + Args: + material_name: 材质名称 + + Returns: + 目标网格名称("Body"或"Face"),如果材质不在映射中返回None + """ + return MATERIAL_MESH_MAPPING.get(material_name) + + +def get_material_index(mesh_obj: "Object", material_name: str) -> Optional[int]: + """ + 获取材质在网格中的索引 + + Args: + mesh_obj: 网格对象 + material_name: 材质名称 + + Returns: + 材质索引,如果不存在返回None + """ + if mesh_obj is None or mesh_obj.type != "MESH": + return None + + for i, slot in enumerate(mesh_obj.material_slots): + if slot.material and slot.material.name == material_name: + return i + + return None + + +# 保留材质关键字常量(不分离的材质:只有SKIN和HAIR) +RETAINED_MATERIAL_KEYWORDS = ["SKIN", "HAIR"] + + def is_skin_material(material_name: str) -> bool: """ 判断材质是否为皮肤材质 @@ -48,6 +149,39 @@ def is_skin_material(material_name: str) -> bool: return "SKIN" in material_name.upper() +def is_retained_material(material_name: str) -> bool: + """ + 判断材质是否为保留材质(不分离) + + 只有SKIN和HAIR材质保留在原Body网格中。 + CLOTH和其他材质都需要分离。 + + Args: + material_name: 材质名称 + + Returns: + 如果材质名包含"SKIN"或"HAIR"则返回True(不区分大小写) + """ + upper_name = material_name.upper() + return any(keyword in upper_name for keyword in RETAINED_MATERIAL_KEYWORDS) + + +def is_separable_material(material_name: str) -> bool: + """ + 判断材质是否为可分离材质 + + 不包含"SKIN"、"HAIR"的材质都是可分离材质, + 包括CLOTH和其他所有材质。 + + Args: + material_name: 材质名称 + + Returns: + 如果材质名不包含"SKIN"、"HAIR"则返回True(包括CLOTH和其他材质) + """ + return not is_retained_material(material_name) + + def get_material_face_count(mesh: "Mesh", material_index: int) -> int: """ 获取指定材质的面数量 @@ -86,3 +220,229 @@ def generate_unique_name(base_name: str, existing_names: set[str]) -> str: if candidate not in existing_names: return candidate suffix += 1 + + +def normalize_uv_pair( + uv1: tuple[float, float], uv2: tuple[float, float] +) -> tuple[tuple[float, float], tuple[float, float]]: + """ + 标准化UV坐标对(较小的在前) + + 通过将较小的UV坐标放在前面,确保相同的边无论顶点顺序如何都能匹配。 + 使用6位小数精度进行比较和存储。 + + Args: + uv1: 第一个UV坐标 (u, v) + uv2: 第二个UV坐标 (u, v) + + Returns: + 标准化后的UV对 ((u1,v1), (u2,v2)),较小的坐标在前 + """ + # 四舍五入到指定精度 + uv1_rounded = (round(uv1[0], UV_PRECISION), round(uv1[1], UV_PRECISION)) + uv2_rounded = (round(uv2[0], UV_PRECISION), round(uv2[1], UV_PRECISION)) + + # 比较并排序,较小的在前 + if uv1_rounded <= uv2_rounded: + return (uv1_rounded, uv2_rounded) + return (uv2_rounded, uv1_rounded) + + +def load_edge_uv_data(material_name: str) -> Optional[set[tuple]]: + """ + 从JSON文件加载边UV数据 + + Args: + material_name: 材质名称(用于定位JSON文件) + + Returns: + UV边坐标集合,格式为 {((u1,v1), (u2,v2)), ...} + 如果文件不存在返回None + """ + # 获取当前模块所在目录 + current_dir = os.path.dirname(os.path.abspath(__file__)) + json_path = os.path.join(current_dir, f"{material_name}.json") + + if not os.path.exists(json_path): + return None + + try: + with open(json_path, "r", encoding="utf-8") as f: + data = json.load(f) + + edge_uvs = data.get("edge_uvs", []) + result: set[tuple] = set() + + for edge_uv in edge_uvs: + if len(edge_uv) == 2 and len(edge_uv[0]) == 2 and len(edge_uv[1]) == 2: + uv1 = (float(edge_uv[0][0]), float(edge_uv[0][1])) + uv2 = (float(edge_uv[1][0]), float(edge_uv[1][1])) + normalized = normalize_uv_pair(uv1, uv2) + result.add(normalized) + + return result + except (json.JSONDecodeError, KeyError, TypeError, ValueError): + return None + + +def select_edges_by_uv( + bm: "BMesh", + uv_layer: "BMLayerItem", + target_uvs: set[tuple], +) -> int: + """ + 根据UV坐标选中边 + + 遍历BMesh中的所有边,比较每条边的loop UV坐标与目标UV集合, + 选中匹配的边。使用6位小数精度进行匹配。 + + Args: + bm: BMesh对象 + uv_layer: UV层 + target_uvs: 目标UV坐标集合,格式为 {((u1,v1), (u2,v2)), ...} + + Returns: + 选中的边数量 + """ + selected_count = 0 + + for edge in bm.edges: + # 获取边的两个顶点关联的loop + # 每条边可能有多个关联的面,我们需要检查所有关联的loop + edge_selected = False + + for face in edge.link_faces: + # 找到这条边在当前面中的两个loop + edge_loops = [] + for loop in face.loops: + if loop.vert in edge.verts: + edge_loops.append(loop) + + if len(edge_loops) == 2: + # 获取两个loop的UV坐标 + uv1_data = edge_loops[0][uv_layer] + uv2_data = edge_loops[1][uv_layer] + + uv1 = (uv1_data.uv[0], uv1_data.uv[1]) + uv2 = (uv2_data.uv[0], uv2_data.uv[1]) + + # 标准化UV对并检查是否在目标集合中 + normalized = normalize_uv_pair(uv1, uv2) + + if normalized in target_uvs: + edge_selected = True + break + + if edge_selected: + edge.select = True + selected_count += 1 + + return selected_count + + +def isolate_material_faces( + context: "Context", + mesh_obj: "Object", + material_name: str, +) -> bool: + """ + 隔离显示指定材质的面(隐藏其他面) + + 进入编辑模式,选择指定材质的所有面,执行隐藏未选中操作, + 然后切换到边选择模式。 + + Args: + context: Blender上下文 + mesh_obj: 网格对象 + material_name: 材质名称 + + Returns: + 是否成功隔离 + + Requirements: 3.1, 3.2, 3.3, 3.4 + """ + import bpy + + if mesh_obj is None or mesh_obj.type != "MESH": + return False + + # 获取材质索引 + material_index = get_material_index(mesh_obj, material_name) + if material_index is None: + return False + + # 保存当前活动对象和选择状态 + original_active = context.view_layer.objects.active + original_mode = context.object.mode if context.object else "OBJECT" + + try: + # 确保在对象模式下开始 + if original_mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + # 取消所有选择,选择目标网格 + bpy.ops.object.select_all(action="DESELECT") + mesh_obj.select_set(True) + context.view_layer.objects.active = mesh_obj + + # 进入编辑模式 (Requirements 3.1) + bpy.ops.object.mode_set(mode="EDIT") + + # 切换到面选择模式 + bpy.ops.mesh.select_mode(type="FACE") + + # 取消所有选择 + bpy.ops.mesh.select_all(action="DESELECT") + + # 设置活动材质索引并选择该材质的所有面 (Requirements 3.2) + mesh_obj.active_material_index = material_index + bpy.ops.object.material_slot_select() + + # 执行隐藏未选中 (Shift+H equivalent) (Requirements 3.3) + bpy.ops.mesh.hide(unselected=True) + + # 切换到边选择模式 (Requirements 3.4) + bpy.ops.mesh.select_mode(type="EDGE") + + return True + + except Exception: + # 发生错误时尝试恢复状态 + try: + if context.object and context.object.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + if original_active: + context.view_layer.objects.active = original_active + except Exception: + pass + return False + + +def select_material_faces( + mesh_obj: "Object", + material_index: int, +) -> set[int]: + """ + 获取指定材质索引的所有面的索引集合 + + 这是一个纯函数,用于测试材质面选择的完整性。 + 不修改任何Blender状态。 + + Args: + mesh_obj: 网格对象 + material_index: 材质索引 + + Returns: + 使用该材质的面索引集合 + """ + if mesh_obj is None or mesh_obj.type != "MESH": + return set() + + mesh = mesh_obj.data + result: set[int] = set() + + for i, polygon in enumerate(mesh.polygons): + if polygon.material_index == material_index: + result.add(i) + + return result diff --git a/registration.py b/registration.py index 9c5314c..598d1ea 100644 --- a/registration.py +++ b/registration.py @@ -434,6 +434,7 @@ classes: list[ ops.VRM_OT_show_blend_file_addon_compatibility_warning, ops.VRM_OT_make_estimated_humanoid_t_pose, body_mesh_ops.VRM_OT_separate_non_skin_faces, + body_mesh_ops.VRM_OT_merge_uv_edges, validation.VrmValidationError, validation.WM_OT_vrm_validator, export_scene.WM_OT_vrm_export_human_bones_assignment, diff --git a/tests/test_uv_matching.py b/tests/test_uv_matching.py new file mode 100644 index 0000000..01565b0 --- /dev/null +++ b/tests/test_uv_matching.py @@ -0,0 +1,728 @@ +# SPDX-License-Identifier: MIT OR GPL-3.0-or-later +""" +Standalone property-based tests for UV edge matching functionality. + +Feature: vrm-uv-edge-merger +Property 5: UV Edge Matching Precision + +These tests can run without Blender dependencies. +""" +from dataclasses import dataclass +from typing import Optional + +from hypothesis import given, settings +from hypothesis import strategies as st + + +# UV坐标精度(小数位数)- copied from utils.py +UV_PRECISION = 6 + + +def normalize_uv_pair( + uv1: tuple[float, float], uv2: tuple[float, float] +) -> tuple[tuple[float, float], tuple[float, float]]: + """ + 标准化UV坐标对(较小的在前) + + 通过将较小的UV坐标放在前面,确保相同的边无论顶点顺序如何都能匹配。 + 使用6位小数精度进行比较和存储。 + + Args: + uv1: 第一个UV坐标 (u, v) + uv2: 第二个UV坐标 (u, v) + + Returns: + 标准化后的UV对 ((u1,v1), (u2,v2)),较小的坐标在前 + """ + # 四舍五入到指定精度 + uv1_rounded = (round(uv1[0], UV_PRECISION), round(uv1[1], UV_PRECISION)) + uv2_rounded = (round(uv2[0], UV_PRECISION), round(uv2[1], UV_PRECISION)) + + # 比较并排序,较小的在前 + if uv1_rounded <= uv2_rounded: + return (uv1_rounded, uv2_rounded) + return (uv2_rounded, uv1_rounded) + + +class TestUvEdgeMatchingPrecision: + """ + Property 5: UV边匹配精度 + *For any* edge with UV coordinates, the matching function SHALL correctly identify + the edge if its UV coordinates (rounded to 6 decimal places) exist in the target UV set. + **Validates: Requirements 4.2, 4.3, 4.4** + """ + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.floats(min_value=-1e-7, max_value=1e-7, allow_nan=False, allow_infinity=False), + ) + @settings(max_examples=100) + def test_uv_matching_precision_property( + self, + uv1: tuple[float, float], + uv2: tuple[float, float], + noise: float, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 5: UV Edge Matching Precision + For any UV coordinate pair, adding noise smaller than 6 decimal precision + should still result in a match after normalization. + """ + # Create the normalized reference UV pair + normalized_ref = normalize_uv_pair(uv1, uv2) + + # Add small noise (within 6 decimal precision tolerance) + uv1_noisy = (uv1[0] + noise, uv1[1] + noise) + uv2_noisy = (uv2[0] + noise, uv2[1] + noise) + + # Normalize the noisy pair + normalized_noisy = normalize_uv_pair(uv1_noisy, uv2_noisy) + + # Create target UV set with the reference + target_uvs = {normalized_ref} + + # The noisy normalized pair should match the reference + # because the noise is within 6 decimal precision + matches = normalized_noisy in target_uvs + + # Calculate expected match based on rounding behavior + # If noise is small enough, rounding should produce same result + expected_match = normalized_ref == normalized_noisy + + assert matches == expected_match, ( + f"UV matching inconsistency: " + f"normalized_ref={normalized_ref}, normalized_noisy={normalized_noisy}, " + f"noise={noise}, matches={matches}, expected={expected_match}" + ) + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + ) + @settings(max_examples=100) + def test_uv_matching_set_membership_property( + self, + uv1: tuple[float, float], + uv2: tuple[float, float], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 5: UV Edge Matching Precision + For any UV coordinate pair, the normalized pair should be found in a target + set containing that same normalized pair, regardless of original order. + """ + # Normalize in both orders + normalized_forward = normalize_uv_pair(uv1, uv2) + normalized_reverse = normalize_uv_pair(uv2, uv1) + + # Create target set with forward normalization + target_uvs = {normalized_forward} + + # Both should match (symmetry property ensures this) + assert normalized_forward in target_uvs, ( + f"Forward normalized pair {normalized_forward} not found in target set" + ) + assert normalized_reverse in target_uvs, ( + f"Reverse normalized pair {normalized_reverse} not found in target set " + f"(forward was {normalized_forward})" + ) + + @given( + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + st.tuples( + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False), + ), + ) + @settings(max_examples=100) + def test_uv_precision_rounding_property( + self, + uv1: tuple[float, float], + uv2: tuple[float, float], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 5: UV Edge Matching Precision + For any UV coordinate pair, the normalized result should have coordinates + rounded to exactly 6 decimal places. + """ + normalized = normalize_uv_pair(uv1, uv2) + + # Check that all coordinates are rounded to 6 decimal places + for uv in normalized: + for coord in uv: + # Verify rounding by checking round(coord, 6) == coord + assert round(coord, UV_PRECISION) == coord, ( + f"Coordinate {coord} is not rounded to {UV_PRECISION} decimal places" + ) + + +def select_material_faces_impl( + polygons_material_indices: list[int], + material_index: int, +) -> set[int]: + """ + 获取指定材质索引的所有面的索引集合 + + 这是一个纯函数实现,用于测试材质面选择的完整性。 + + Args: + polygons_material_indices: 每个面的材质索引列表 + material_index: 目标材质索引 + + Returns: + 使用该材质的面索引集合 + """ + result: set[int] = set() + for i, mat_idx in enumerate(polygons_material_indices): + if mat_idx == material_index: + result.add(i) + return result + + +class TestMaterialFaceSelectionCompleteness: + """ + Property 6: Material Face Selection Completeness + *For any* mesh and material index, the face selection function SHALL select + exactly all faces that have the specified material index, and no others. + **Validates: Requirements 3.2** + """ + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=1, + max_size=50, + ), + st.integers(min_value=0, max_value=5), + ) + @settings(max_examples=100) + def test_material_face_selection_completeness_property( + self, + material_indices: list[int], + target_material_index: int, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh with faces having various material indices, select_material_faces + returns exactly the set of face indices that have the specified material index. + """ + # Call the function under test + result = select_material_faces_impl(material_indices, target_material_index) + + # Calculate expected result: indices of faces with the target material + expected = { + i for i, mat_idx in enumerate(material_indices) + if mat_idx == target_material_index + } + + # Verify completeness: result should contain exactly the expected faces + assert result == expected, ( + f"select_material_faces returned {result}, expected {expected}. " + f"Material indices: {material_indices}, target: {target_material_index}" + ) + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=0, + max_size=50, + ), + ) + @settings(max_examples=100) + def test_material_face_selection_no_false_positives_property( + self, + material_indices: list[int], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh, selecting faces for a material index that doesn't exist + should return an empty set. + """ + # Use a material index that definitely doesn't exist + non_existent_index = max(material_indices, default=-1) + 10 + + # Call the function under test + result = select_material_faces_impl(material_indices, non_existent_index) + + # Should return empty set for non-existent material + assert result == set(), ( + f"select_material_faces returned {result} for non-existent material " + f"index {non_existent_index}, expected empty set" + ) + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=1, + max_size=50, + ), + st.integers(min_value=0, max_value=5), + ) + @settings(max_examples=100) + def test_material_face_selection_no_duplicates_property( + self, + material_indices: list[int], + target_material_index: int, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh, the selected face indices should be unique (no duplicates). + """ + result = select_material_faces_impl(material_indices, target_material_index) + + # Since result is a set, it inherently has no duplicates + # But we verify the count matches expected + expected_count = sum( + 1 for mat_idx in material_indices if mat_idx == target_material_index + ) + + assert len(result) == expected_count, ( + f"Expected {expected_count} faces, got {len(result)}. " + f"Material indices: {material_indices}, target: {target_material_index}" + ) + + @given( + st.lists( + st.integers(min_value=0, max_value=5), + min_size=1, + max_size=50, + ), + st.integers(min_value=0, max_value=5), + ) + @settings(max_examples=100) + def test_material_face_selection_valid_indices_property( + self, + material_indices: list[int], + target_material_index: int, + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 6: Material Face Selection Completeness + For any mesh, all returned face indices should be valid (within bounds). + """ + result = select_material_faces_impl(material_indices, target_material_index) + + # All indices should be valid + for face_idx in result: + assert 0 <= face_idx < len(material_indices), ( + f"Invalid face index {face_idx} for mesh with " + f"{len(material_indices)} faces" + ) + + +def find_body_mesh_impl(children: list[tuple[str, str]]) -> bool: + """ + 检查子对象列表中是否存在名为"Body"的MESH对象 + + Args: + children: 子对象列表,每个元素为 (name, type) 元组 + + Returns: + 如果存在名为"Body"的MESH对象返回True,否则返回False + """ + for name, obj_type in children: + if name == "Body" and obj_type == "MESH": + return True + return False + + +def find_face_mesh_impl(children: list[tuple[str, str]]) -> bool: + """ + 检查子对象列表中是否存在名为"Face"的MESH对象 + + Args: + children: 子对象列表,每个元素为 (name, type) 元组 + + Returns: + 如果存在名为"Face"的MESH对象返回True,否则返回False + """ + for name, obj_type in children: + if name == "Face" and obj_type == "MESH": + return True + return False + + +def is_valid_vrm_structure(children: list[tuple[str, str]]) -> bool: + """ + 检查子对象列表是否构成有效的VRM结构 + + Args: + children: 子对象列表,每个元素为 (name, type) 元组 + + Returns: + 如果同时存在Body和Face MESH对象返回True,否则返回False + """ + return find_body_mesh_impl(children) and find_face_mesh_impl(children) + + +class TestVRMModelDetectionCorrectness: + """ + Property 1: VRM Model Detection Correctness + *For any* Blender object hierarchy, the VRM detection function SHALL return True + if and only if the hierarchy contains an Armature with both "Body" and "Face" + mesh children, regardless of the current Blender mode. + **Validates: Requirements 1.1, 1.2, 1.3, 1.4** + """ + + @given( + st.lists( + st.tuples( + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + st.sampled_from(["MESH", "ARMATURE", "EMPTY", "LIGHT", "CAMERA"]), + ), + min_size=0, + max_size=10, + ), + ) + @settings(max_examples=100) + def test_vrm_detection_requires_both_body_and_face( + self, + children_specs: list[tuple[str, str]], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + For any set of child objects, VRM detection returns True if and only if + both "Body" and "Face" mesh children exist. + """ + # Check if Body and Face meshes exist + has_body_mesh = any( + name == "Body" and obj_type == "MESH" + for name, obj_type in children_specs + ) + has_face_mesh = any( + name == "Face" and obj_type == "MESH" + for name, obj_type in children_specs + ) + + # Test find_body_mesh_impl + body_found = find_body_mesh_impl(children_specs) + assert body_found == has_body_mesh, ( + f"find_body_mesh_impl mismatch: expected {has_body_mesh}, " + f"got {body_found} for {children_specs}" + ) + + # Test find_face_mesh_impl + face_found = find_face_mesh_impl(children_specs) + assert face_found == has_face_mesh, ( + f"find_face_mesh_impl mismatch: expected {has_face_mesh}, " + f"got {face_found} for {children_specs}" + ) + + # VRM detection should be True only if both exist + expected_vrm_valid = has_body_mesh and has_face_mesh + actual_vrm_valid = is_valid_vrm_structure(children_specs) + assert actual_vrm_valid == expected_vrm_valid, ( + f"VRM detection mismatch: expected {expected_vrm_valid}, " + f"got {actual_vrm_valid} for {children_specs}" + ) + + def test_vrm_detection_with_valid_vrm_structure(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + A valid VRM structure with both Body and Face meshes should be detected. + """ + children = [ + ("Body", "MESH"), + ("Face", "MESH"), + ("Hair", "MESH"), + ] + + assert find_body_mesh_impl(children), "Body mesh should be found" + assert find_face_mesh_impl(children), "Face mesh should be found" + assert is_valid_vrm_structure(children), "VRM structure should be valid" + + def test_vrm_detection_with_missing_body(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Missing Body mesh should result in invalid VRM detection. + """ + children = [ + ("Face", "MESH"), + ("Hair", "MESH"), + ] + + assert not find_body_mesh_impl(children), "Body mesh should not be found" + assert find_face_mesh_impl(children), "Face mesh should be found" + assert not is_valid_vrm_structure(children), "VRM structure should be invalid" + + def test_vrm_detection_with_missing_face(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Missing Face mesh should result in invalid VRM detection. + """ + children = [ + ("Body", "MESH"), + ("Hair", "MESH"), + ] + + assert find_body_mesh_impl(children), "Body mesh should be found" + assert not find_face_mesh_impl(children), "Face mesh should not be found" + assert not is_valid_vrm_structure(children), "VRM structure should be invalid" + + def test_vrm_detection_with_non_mesh_body(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Body object that is not a MESH should not be detected as Body mesh. + """ + children = [ + ("Body", "EMPTY"), # Not a mesh + ("Face", "MESH"), + ] + + assert not find_body_mesh_impl(children), "Non-mesh Body should not be found" + assert find_face_mesh_impl(children), "Face mesh should be found" + assert not is_valid_vrm_structure(children), "VRM structure should be invalid" + + def test_vrm_detection_with_non_mesh_face(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Face object that is not a MESH should not be detected as Face mesh. + """ + children = [ + ("Body", "MESH"), + ("Face", "ARMATURE"), # Not a mesh + ] + + assert find_body_mesh_impl(children), "Body mesh should be found" + assert not find_face_mesh_impl(children), "Non-mesh Face should not be found" + assert not is_valid_vrm_structure(children), "VRM structure should be invalid" + + def test_vrm_detection_with_empty_children(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 1: VRM Model Detection Correctness + Empty children list should result in invalid VRM detection. + """ + children: list[tuple[str, str]] = [] + + assert not find_body_mesh_impl(children), "Body should not be found in empty list" + assert not find_face_mesh_impl(children), "Face should not be found in empty list" + assert not is_valid_vrm_structure(children), "VRM structure should be invalid" + + +@dataclass +class MockOperationState: + """Mock operation state for testing state restoration.""" + original_mode: str + original_active_object: Optional[str] # Object name + original_selected_objects: list[str] # Object names + processed_materials: list[str] + + +def save_state_impl( + current_mode: str, + active_object_name: Optional[str], + selected_object_names: list[str], +) -> MockOperationState: + """ + 保存当前状态的纯函数实现 + + Args: + current_mode: 当前Blender模式 + active_object_name: 当前活动对象名称 + selected_object_names: 当前选中对象名称列表 + + Returns: + 保存的状态对象 + """ + return MockOperationState( + original_mode=current_mode, + original_active_object=active_object_name, + original_selected_objects=list(selected_object_names), + processed_materials=[], + ) + + +def restore_state_impl( + state: MockOperationState, + available_objects: set[str], +) -> tuple[str, Optional[str], list[str]]: + """ + 恢复状态的纯函数实现 + + Args: + state: 保存的状态 + available_objects: 当前可用的对象名称集合 + + Returns: + (恢复的模式, 恢复的活动对象, 恢复的选中对象列表) + """ + # 恢复选中对象(只恢复仍然存在的对象) + restored_selected = [ + name for name in state.original_selected_objects + if name in available_objects + ] + + # 恢复活动对象(只有在对象仍然存在时才恢复) + restored_active = None + if state.original_active_object and state.original_active_object in available_objects: + restored_active = state.original_active_object + + # 恢复模式 + restored_mode = state.original_mode + + return (restored_mode, restored_active, restored_selected) + + +class TestStateRestorationOnError: + """ + Property 7: State Restoration on Error + *For any* error during processing, the system SHALL restore the original Blender + state (mode, active object, selection) before reporting the error. + **Validates: Requirements 7.4** + """ + + @given( + st.sampled_from(["OBJECT", "EDIT", "SCULPT", "VERTEX_PAINT", "WEIGHT_PAINT"]), + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()) | st.none(), + st.lists( + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + min_size=0, + max_size=5, + ), + ) + @settings(max_examples=100) + def test_state_save_and_restore_property( + self, + original_mode: str, + active_object: Optional[str], + selected_objects: list[str], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 7: State Restoration on Error + For any initial state, saving and then restoring should return to the + original state (assuming all objects still exist). + """ + # Save state + state = save_state_impl(original_mode, active_object, selected_objects) + + # Verify state was saved correctly + assert state.original_mode == original_mode + assert state.original_active_object == active_object + assert state.original_selected_objects == selected_objects + + # Create available objects set (all objects exist) + available_objects = set(selected_objects) + if active_object: + available_objects.add(active_object) + + # Restore state + restored_mode, restored_active, restored_selected = restore_state_impl( + state, available_objects + ) + + # Verify restoration + assert restored_mode == original_mode, ( + f"Mode not restored: expected {original_mode}, got {restored_mode}" + ) + assert restored_active == active_object, ( + f"Active object not restored: expected {active_object}, got {restored_active}" + ) + assert set(restored_selected) == set(selected_objects), ( + f"Selected objects not restored: expected {selected_objects}, " + f"got {restored_selected}" + ) + + @given( + st.sampled_from(["OBJECT", "EDIT", "SCULPT"]), + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + st.lists( + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + min_size=1, + max_size=5, + ), + st.sets( + st.text(min_size=1, max_size=20).filter(lambda x: x.strip()), + min_size=0, + max_size=3, + ), + ) + @settings(max_examples=100) + def test_state_restore_with_deleted_objects_property( + self, + original_mode: str, + active_object: str, + selected_objects: list[str], + deleted_objects: set[str], + ) -> None: + """ + Feature: vrm-uv-edge-merger, Property 7: State Restoration on Error + When some objects have been deleted, restoration should only restore + objects that still exist. + """ + # Save state + state = save_state_impl(original_mode, active_object, selected_objects) + + # Create available objects (some may have been deleted) + all_objects = set(selected_objects) + all_objects.add(active_object) + available_objects = all_objects - deleted_objects + + # Restore state + restored_mode, restored_active, restored_selected = restore_state_impl( + state, available_objects + ) + + # Mode should always be restored + assert restored_mode == original_mode + + # Active object should only be restored if it still exists + if active_object in deleted_objects: + assert restored_active is None, ( + f"Deleted active object should not be restored: {active_object}" + ) + else: + assert restored_active == active_object + + # Selected objects should only include those that still exist + for obj_name in restored_selected: + assert obj_name in available_objects, ( + f"Restored selected object {obj_name} should be in available objects" + ) + assert obj_name not in deleted_objects, ( + f"Deleted object {obj_name} should not be in restored selection" + ) + + def test_state_restore_with_empty_state(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 7: State Restoration on Error + Restoring an empty state should work without errors. + """ + state = save_state_impl("OBJECT", None, []) + available_objects: set[str] = set() + + restored_mode, restored_active, restored_selected = restore_state_impl( + state, available_objects + ) + + assert restored_mode == "OBJECT" + assert restored_active is None + assert restored_selected == [] + + def test_state_processed_materials_tracking(self) -> None: + """ + Feature: vrm-uv-edge-merger, Property 7: State Restoration on Error + The state should track processed materials for error reporting. + """ + state = save_state_impl("OBJECT", "Armature", ["Body", "Face"]) + + # Simulate processing materials + state.processed_materials.append("Material1") + state.processed_materials.append("Material2") + + assert len(state.processed_materials) == 2 + assert "Material1" in state.processed_materials + assert "Material2" in state.processed_materials