Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'uv_magic_uv/op')
-rw-r--r--uv_magic_uv/op/__init__.py6
-rw-r--r--uv_magic_uv/op/align_uv.py389
-rw-r--r--uv_magic_uv/op/align_uv_cursor.py111
-rw-r--r--uv_magic_uv/op/copy_paste_uv.py883
-rw-r--r--uv_magic_uv/op/copy_paste_uv_object.py312
-rw-r--r--uv_magic_uv/op/copy_paste_uv_uvedit.py78
-rw-r--r--uv_magic_uv/op/flip_rotate_uv.py62
-rw-r--r--uv_magic_uv/op/mirror_uv.py67
-rw-r--r--uv_magic_uv/op/move_uv.py95
-rw-r--r--uv_magic_uv/op/pack_uv.py76
-rw-r--r--uv_magic_uv/op/preserve_uv_aspect.py86
-rw-r--r--uv_magic_uv/op/select_uv.py161
-rw-r--r--uv_magic_uv/op/smooth_uv.py76
-rw-r--r--uv_magic_uv/op/texture_lock.py305
-rw-r--r--uv_magic_uv/op/texture_projection.py245
-rw-r--r--uv_magic_uv/op/texture_wrap.py102
-rw-r--r--uv_magic_uv/op/transfer_uv.py110
-rw-r--r--uv_magic_uv/op/unwrap_constraint.py68
-rw-r--r--uv_magic_uv/op/uv_bounding_box.py374
-rw-r--r--uv_magic_uv/op/uv_inspection.py643
-rw-r--r--uv_magic_uv/op/uv_sculpt.py271
-rw-r--r--uv_magic_uv/op/uvw.py72
-rw-r--r--uv_magic_uv/op/world_scale_uv.py666
23 files changed, 3509 insertions, 1749 deletions
diff --git a/uv_magic_uv/op/__init__.py b/uv_magic_uv/op/__init__.py
index 75885ef6..9535b76d 100644
--- a/uv_magic_uv/op/__init__.py
+++ b/uv_magic_uv/op/__init__.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
if "bpy" in locals():
import importlib
@@ -35,6 +35,7 @@ if "bpy" in locals():
importlib.reload(move_uv)
importlib.reload(pack_uv)
importlib.reload(preserve_uv_aspect)
+ importlib.reload(select_uv)
importlib.reload(smooth_uv)
importlib.reload(texture_lock)
importlib.reload(texture_projection)
@@ -57,6 +58,7 @@ else:
from . import move_uv
from . import pack_uv
from . import preserve_uv_aspect
+ from . import select_uv
from . import smooth_uv
from . import texture_lock
from . import texture_projection
diff --git a/uv_magic_uv/op/align_uv.py b/uv_magic_uv/op/align_uv.py
index dcfb57c3..90168a56 100644
--- a/uv_magic_uv/op/align_uv.py
+++ b/uv_magic_uv/op/align_uv.py
@@ -20,8 +20,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import math
from math import atan2, tan, sin, cos
@@ -29,11 +29,42 @@ from math import atan2, tan, sin, cos
import bpy
import bmesh
from mathutils import Vector
-from bpy.props import EnumProperty, BoolProperty
+from bpy.props import EnumProperty, BoolProperty, FloatProperty
from .. import common
+__all__ = [
+ 'Properties',
+ 'OperatorCircle',
+ 'OperatorStraighten',
+ 'OperatorAxis',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
# get sum vertex length of loop sequences
def get_loop_vert_len(loops):
length = 0
@@ -86,10 +117,69 @@ def calc_v_on_circle(v, center, radius):
return new_v
-class MUV_AUVCircle(bpy.types.Operator):
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_align_uv_enabled = BoolProperty(
+ name="Align UV Enabled",
+ description="Align UV is enabled",
+ default=False
+ )
+ scene.muv_align_uv_transmission = BoolProperty(
+ name="Transmission",
+ description="Align linked UVs",
+ default=False
+ )
+ scene.muv_align_uv_select = BoolProperty(
+ name="Select",
+ description="Select UVs which are aligned",
+ default=False
+ )
+ scene.muv_align_uv_vertical = BoolProperty(
+ name="Vert-Infl (Vertical)",
+ description="Align vertical direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ scene.muv_align_uv_horizontal = BoolProperty(
+ name="Vert-Infl (Horizontal)",
+ description="Align horizontal direction influenced "
+ "by mesh vertex proportion",
+ default=False
+ )
+ scene.muv_align_uv_mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
+ scene.muv_align_uv_location = EnumProperty(
+ name="Location",
+ description="Align location",
+ items=[
+ ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
+ ('MIDDLE', "Middle", "Align to middle"),
+ ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
+ ],
+ default='MIDDLE'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_align_uv_enabled
+ del scene.muv_align_uv_transmission
+ del scene.muv_align_uv_select
+ del scene.muv_align_uv_vertical
+ del scene.muv_align_uv_horizontal
+ del scene.muv_align_uv_mesh_infl
+ del scene.muv_align_uv_location
+
+
+class OperatorCircle(bpy.types.Operator):
- bl_idname = "uv.muv_auv_circle"
- bl_label = "Circle"
+ bl_idname = "uv.muv_align_uv_operator_circle"
+ bl_label = "Align UV (Circle)"
bl_description = "Align UV coordinates to Circle"
bl_options = {'REGISTER', 'UNDO'}
@@ -106,7 +196,10 @@ class MUV_AUVCircle(bpy.types.Operator):
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
obj = context.active_object
@@ -167,66 +260,164 @@ class MUV_AUVCircle(bpy.types.Operator):
return {'FINISHED'}
+# get accumulate vertex lengths of loop sequences
+def get_loop_vert_accum_len(loops):
+ accum_lengths = [0.0]
+ length = 0
+ for l1, l2 in zip(loops[:-1], loops[1:]):
+ diff = l2.vert.co - l1.vert.co
+ length = length + abs(diff.length)
+ accum_lengths.extend([length])
+
+ return accum_lengths
+
+
+# get sum uv length of loop sequences
+def get_loop_uv_accum_len(loops, uv_layer):
+ accum_lengths = [0.0]
+ length = 0
+ for l1, l2 in zip(loops[:-1], loops[1:]):
+ diff = l2[uv_layer].uv - l1[uv_layer].uv
+ length = length + abs(diff.length)
+ accum_lengths.extend([length])
+
+ return accum_lengths
+
+
# get horizontal differential of UV influenced by mesh vertex
-def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
common.debug_print(
- "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
+ "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx, vidx, pidx))
- # get total vertex length
- hloops = []
- for s in loop_seqs:
- hloops.extend([s[vidx][0], s[vidx][1]])
- vert_total_hlen = get_loop_vert_len(hloops)
- common.debug_print(vert_total_hlen)
+ base_uv = loop_seqs[0][vidx][0][uv_layer].uv.copy()
- # target vertex length
+ # calculate original length
hloops = []
- for s in loop_seqs[:hidx]:
+ for s in loop_seqs:
hloops.extend([s[vidx][0], s[vidx][1]])
- for pidx, l in enumerate(loop_seqs[hidx][vidx]):
- if pidx > pair_idx:
+ total_vlen = get_loop_vert_len(hloops)
+ accum_vlens = get_loop_vert_accum_len(hloops)
+ total_uvlen = get_loop_uv_len(hloops, uv_layer)
+ accum_uvlens = get_loop_uv_accum_len(hloops, uv_layer)
+ orig_uvs = [l[uv_layer].uv.copy() for l in hloops]
+
+ # calculate target length
+ tgt_noinfl = total_uvlen * (hidx + pidx) / len(loop_seqs)
+ tgt_infl = total_uvlen * accum_vlens[hidx * 2 + pidx] / total_vlen
+ target_length = tgt_noinfl * (1 - infl) + tgt_infl * infl
+ common.debug_print(target_length)
+ common.debug_print(accum_uvlens)
+
+ # calculate target UV
+ for i in range(len(accum_uvlens[:-1])):
+ # get line segment which UV will be placed
+ if ((accum_uvlens[i] <= target_length) and
+ (accum_uvlens[i + 1] > target_length)):
+ tgt_seg_len = target_length - accum_uvlens[i]
+ seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+ uv1 = orig_uvs[i]
+ uv2 = orig_uvs[i + 1]
+ target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+ break
+ elif i == (len(accum_uvlens[:-1]) - 1):
+ if accum_uvlens[i + 1] != target_length:
+ raise Exception(
+ "Internal Error: horizontal_target_length={}"
+ " is not equal to {}"
+ .format(target_length, accum_uvlens[-1]))
+ tgt_seg_len = target_length - accum_uvlens[i]
+ seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+ uv1 = orig_uvs[i]
+ uv2 = orig_uvs[i + 1]
+ target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
break
- hloops.append(l)
- vert_hlen = get_loop_vert_len(hloops)
- common.debug_print(vert_hlen)
+ else:
+ raise Exception("Internal Error: horizontal_target_length={}"
+ " is not in range {} to {}"
+ .format(target_length, accum_uvlens[0],
+ accum_uvlens[-1]))
- # get total UV length
- # uv_all_hdiff = loop_seqs[-1][0][-1][uv_layer].uv -
- # loop_seqs[0][0][0][uv_layer].uv
- uv_total_hlen = loop_seqs[-1][vidx][-1][uv_layer].uv -\
- loop_seqs[0][vidx][0][uv_layer].uv
- common.debug_print(uv_total_hlen)
+ return target_uv
- return uv_total_hlen * vert_hlen / vert_total_hlen
+
+# --------------------- LOOP STRUCTURE ----------------------
+#
+# loops[hidx][vidx][pidx]
+# hidx: horizontal index
+# vidx: vertical index
+# pidx: pair index
+#
+# <----- horizontal ----->
+#
+# (hidx, vidx, pidx) = (0, 3, 0)
+# | (hidx, vidx, pidx) = (1, 3, 0)
+# v v
+# ^ o --- oo --- o
+# | | || |
+# vertical | o --- oo --- o <- (hidx, vidx, pidx)
+# | o --- oo --- o = (1, 2, 1)
+# | | || |
+# v o --- oo --- o
+# ^ ^
+# | (hidx, vidx, pidx) = (1, 0, 1)
+# (hidx, vidx, pidx) = (0, 0, 0)
+#
+# -----------------------------------------------------------
# get vertical differential of UV influenced by mesh vertex
-def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx):
+def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
common.debug_print(
- "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx))
-
- # get total vertex length
- hloops = []
- for s in loop_seqs[hidx]:
- hloops.append(s[pair_idx])
- vert_total_hlen = get_loop_vert_len(hloops)
- common.debug_print(vert_total_hlen)
+ "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx, vidx, pidx))
- # target vertex length
- hloops = []
- for s in loop_seqs[hidx][:vidx + 1]:
- hloops.append(s[pair_idx])
- vert_hlen = get_loop_vert_len(hloops)
- common.debug_print(vert_hlen)
+ base_uv = loop_seqs[hidx][0][pidx][uv_layer].uv.copy()
- # get total UV length
- # uv_all_hdiff = loop_seqs[0][-1][pair_idx][uv_layer].uv - \
- # loop_seqs[0][0][pair_idx][uv_layer].uv
- uv_total_hlen = loop_seqs[hidx][-1][pair_idx][uv_layer].uv -\
- loop_seqs[hidx][0][pair_idx][uv_layer].uv
- common.debug_print(uv_total_hlen)
+ # calculate original length
+ vloops = []
+ for s in loop_seqs[hidx]:
+ vloops.append(s[pidx])
+ total_vlen = get_loop_vert_len(vloops)
+ accum_vlens = get_loop_vert_accum_len(vloops)
+ total_uvlen = get_loop_uv_len(vloops, uv_layer)
+ accum_uvlens = get_loop_uv_accum_len(vloops, uv_layer)
+ orig_uvs = [l[uv_layer].uv.copy() for l in vloops]
+
+ # calculate target length
+ tgt_noinfl = total_uvlen * int((vidx + 1) / 2) / len(loop_seqs)
+ tgt_infl = total_uvlen * accum_vlens[vidx] / total_vlen
+ target_length = tgt_noinfl * (1 - infl) + tgt_infl * infl
+ common.debug_print(target_length)
+ common.debug_print(accum_uvlens)
+
+ # calculate target UV
+ for i in range(len(accum_uvlens[:-1])):
+ # get line segment which UV will be placed
+ if ((accum_uvlens[i] <= target_length) and
+ (accum_uvlens[i + 1] > target_length)):
+ tgt_seg_len = target_length - accum_uvlens[i]
+ seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+ uv1 = orig_uvs[i]
+ uv2 = orig_uvs[i + 1]
+ target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+ break
+ elif i == (len(accum_uvlens[:-1]) - 1):
+ if accum_uvlens[i + 1] != target_length:
+ raise Exception("Internal Error: horizontal_target_length={}"
+ " is not equal to {}"
+ .format(target_length, accum_uvlens[-1]))
+ tgt_seg_len = target_length - accum_uvlens[i]
+ seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
+ uv1 = orig_uvs[i]
+ uv2 = orig_uvs[i + 1]
+ target_uv = (uv1 - base_uv) + (uv2 - uv1) * tgt_seg_len / seg_len
+ break
+ else:
+ raise Exception("Internal Error: horizontal_target_length={}"
+ " is not in range {} to {}"
+ .format(target_length, accum_uvlens[0],
+ accum_uvlens[-1]))
- return uv_total_hlen * vert_hlen / vert_total_hlen
+ return target_uv
# get horizontal differential of UV no influenced
@@ -246,10 +437,10 @@ def get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx):
return int((vidx + 1) / 2) * v_uv / (len(hseq) / 2)
-class MUV_AUVStraighten(bpy.types.Operator):
+class OperatorStraighten(bpy.types.Operator):
- bl_idname = "uv.muv_auv_straighten"
- bl_label = "Straighten"
+ bl_idname = "uv.muv_align_uv_operator_straighten"
+ bl_label = "Align UV (Straighten)"
bl_description = "Straighten UV coordinates"
bl_options = {'REGISTER', 'UNDO'}
@@ -275,10 +466,20 @@ class MUV_AUVStraighten(bpy.types.Operator):
"by mesh vertex proportion",
default=False
)
+ mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
# selected and paralleled UV loop sequence will be aligned
def __align_w_transmission(self, loop_seqs, uv_layer):
@@ -293,12 +494,14 @@ class MUV_AUVStraighten(bpy.types.Operator):
for vidx in range(0, len(hseq), 2):
if self.horizontal:
hdiff_uvs = [
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
else:
hdiff_uvs = [
@@ -309,12 +512,14 @@ class MUV_AUVStraighten(bpy.types.Operator):
]
if self.vertical:
vdiff_uvs = [
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
else:
vdiff_uvs = [
@@ -382,10 +587,10 @@ class MUV_AUVStraighten(bpy.types.Operator):
return {'FINISHED'}
-class MUV_AUVAxis(bpy.types.Operator):
+class OperatorAxis(bpy.types.Operator):
- bl_idname = "uv.muv_auv_axis"
- bl_label = "XY-Axis"
+ bl_idname = "uv.muv_align_uv_operator_axis"
+ bl_label = "Align UV (XY-Axis)"
bl_description = "Align UV to XY-axis"
bl_options = {'REGISTER', 'UNDO'}
@@ -421,10 +626,20 @@ class MUV_AUVAxis(bpy.types.Operator):
],
default='MIDDLE'
)
+ mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
# get min/max of UV
def __get_uv_max_min(self, loop_seqs, uv_layer):
@@ -579,12 +794,14 @@ class MUV_AUVAxis(bpy.types.Operator):
for vidx in range(0, len(hseq), 2):
if self.horizontal:
hdiff_uvs = [
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
hdiff_uvs[0].y = hdiff_uvs[0].y + offset_uvs[hidx][0].y
hdiff_uvs[1].y = hdiff_uvs[1].y + offset_uvs[hidx][1].y
@@ -599,12 +816,14 @@ class MUV_AUVAxis(bpy.types.Operator):
]
if self.vertical:
vdiff_uvs = [
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
else:
vdiff_uvs = [
@@ -664,12 +883,14 @@ class MUV_AUVAxis(bpy.types.Operator):
for vidx in range(0, len(hseq), 2):
if self.horizontal:
hdiff_uvs = [
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
hdiff_uvs[0].x = hdiff_uvs[0].x + offset_uvs[hidx][0].x
hdiff_uvs[1].x = hdiff_uvs[1].x + offset_uvs[hidx][1].x
@@ -684,12 +905,14 @@ class MUV_AUVAxis(bpy.types.Operator):
]
if self.vertical:
vdiff_uvs = [
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0),
- get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0,
+ self.mesh_infl),
+ get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1,
+ self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 0),
+ hidx, 0, self.mesh_infl),
get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1,
- hidx, 1),
+ hidx, 1, self.mesh_infl),
]
else:
vdiff_uvs = [
diff --git a/uv_magic_uv/op/align_uv_cursor.py b/uv_magic_uv/op/align_uv_cursor.py
index cae1c89a..d787bde9 100644
--- a/uv_magic_uv/op/align_uv_cursor.py
+++ b/uv_magic_uv/op/align_uv_cursor.py
@@ -20,21 +20,111 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
from mathutils import Vector
-from bpy.props import EnumProperty
+from bpy.props import EnumProperty, BoolProperty, FloatVectorProperty
import bmesh
from .. import common
-class MUV_AUVCAlignOps(bpy.types.Operator):
-
- bl_idname = "uv.muv_auvc_align"
- bl_label = "Align"
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ def auvc_get_cursor_loc(self):
+ area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ bd_size = common.get_uvimg_editor_board_size(area)
+ loc = space.cursor_location
+ if bd_size[0] < 0.000001:
+ cx = 0.0
+ else:
+ cx = loc[0] / bd_size[0]
+ if bd_size[1] < 0.000001:
+ cy = 0.0
+ else:
+ cy = loc[1] / bd_size[1]
+ self['muv_align_uv_cursor_cursor_loc'] = Vector((cx, cy))
+ return self.get('muv_align_uv_cursor_cursor_loc', (0.0, 0.0))
+
+ def auvc_set_cursor_loc(self, value):
+ self['muv_align_uv_cursor_cursor_loc'] = value
+ area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ bd_size = common.get_uvimg_editor_board_size(area)
+ cx = bd_size[0] * value[0]
+ cy = bd_size[1] * value[1]
+ space.cursor_location = Vector((cx, cy))
+
+ scene.muv_align_uv_cursor_enabled = BoolProperty(
+ name="Align UV Cursor Enabled",
+ description="Align UV Cursor is enabled",
+ default=False
+ )
+
+ scene.muv_align_uv_cursor_cursor_loc = FloatVectorProperty(
+ name="UV Cursor Location",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ get=auvc_get_cursor_loc,
+ set=auvc_set_cursor_loc
+ )
+ scene.muv_align_uv_cursor_align_method = EnumProperty(
+ name="Align Method",
+ description="Align Method",
+ default='TEXTURE',
+ items=[
+ ('TEXTURE', "Texture", "Align to texture"),
+ ('UV', "UV", "Align to UV"),
+ ('UV_SEL', "UV (Selected)", "Align to Selected UV")
+ ]
+ )
+
+ scene.muv_uv_cursor_location_enabled = BoolProperty(
+ name="UV Cursor Location Enabled",
+ description="UV Cursor Location is enabled",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_align_uv_cursor_enabled
+ del scene.muv_align_uv_cursor_cursor_loc
+ del scene.muv_align_uv_cursor_align_method
+
+ del scene.muv_uv_cursor_location_enabled
+
+
+class Operator(bpy.types.Operator):
+
+ bl_idname = "uv.muv_align_uv_cursor_operator"
+ bl_label = "Align UV Cursor"
bl_description = "Align cursor to the center of UV island"
bl_options = {'REGISTER', 'UNDO'}
@@ -65,6 +155,13 @@ class MUV_AUVCAlignOps(bpy.types.Operator):
default='TEXTURE'
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
'IMAGE_EDITOR')
diff --git a/uv_magic_uv/op/copy_paste_uv.py b/uv_magic_uv/op/copy_paste_uv.py
index ee89b5e9..cc1baa30 100644
--- a/uv_magic_uv/op/copy_paste_uv.py
+++ b/uv_magic_uv/op/copy_paste_uv.py
@@ -20,11 +20,9 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
-import math
-from math import atan2, sin, cos
import bpy
import bmesh
@@ -34,104 +32,293 @@ from bpy.props import (
IntProperty,
EnumProperty,
)
-from mathutils import Vector
from .. import common
-class MUV_CPUVCopyUV(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'OpeartorCopyUV',
+ 'MenuCopyUV',
+ 'OperatorPasteUV',
+ 'MenuPasteUV',
+ 'OperatorSelSeqCopyUV',
+ 'MenuSelSeqCopyUV',
+ 'OperatorSelSeqPasteUV',
+ 'MenuSelSeqPasteUV',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def get_copy_uv_layers(ops_obj, bm):
+ uv_layers = []
+ if ops_obj.uv_map == "__default":
+ if not bm.loops.layers.uv:
+ ops_obj.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv.verify())
+ ops_obj.report({'INFO'}, "Copy UV coordinate")
+ elif ops_obj.uv_map == "__all":
+ for uv in bm.loops.layers.uv.keys():
+ uv_layers.append(bm.loops.layers.uv[uv])
+ ops_obj.report({'INFO'}, "Copy UV coordinate (UV map: ALL)")
+ else:
+ uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map])
+ ops_obj.report(
+ {'INFO'}, "Copy UV coordinate (UV map:{})".format(ops_obj.uv_map))
+
+ return uv_layers
+
+
+def get_paste_uv_layers(ops_obj, obj, bm, src_info):
+ uv_layers = []
+ if ops_obj.uv_map == "__default":
+ if not bm.loops.layers.uv:
+ ops_obj.report(
+ {'WARNING'}, "Object must have more than one UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv.verify())
+ ops_obj.report({'INFO'}, "Paste UV coordinate")
+ elif ops_obj.uv_map == "__new":
+ new_uv_map = common.create_new_uv_map(obj)
+ if not new_uv_map:
+ ops_obj.report({'WARNING'},
+ "Reached to the maximum number of UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv[new_uv_map.name])
+ ops_obj.report(
+ {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map))
+ elif ops_obj.uv_map == "__all":
+ for src_layer in src_info.keys():
+ if src_layer not in bm.loops.layers.uv.keys():
+ new_uv_map = common.create_new_uv_map(obj, src_layer)
+ if not new_uv_map:
+ ops_obj.report({'WARNING'},
+ "Reached to the maximum number of UV map")
+ return None
+ uv_layers.append(bm.loops.layers.uv[src_layer])
+ ops_obj.report({'INFO'}, "Paste UV coordinate (UV map: ALL)")
+ else:
+ uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map])
+ ops_obj.report(
+ {'INFO'}, "Paste UV coordinate (UV map:{})".format(ops_obj.uv_map))
+
+ return uv_layers
+
+
+def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip,
+ rotate, copy_seams):
+ for slayer_name, dlayer in zip(src_info.keys(), uv_layers):
+ src_faces = src_info[slayer_name]
+ dest_faces = dest_info[dlayer.name]
+
+ for idx, dinfo in enumerate(dest_faces):
+ sinfo = None
+ if strategy == 'N_N':
+ sinfo = src_faces[idx]
+ elif strategy == 'N_M':
+ sinfo = src_faces[idx % len(src_faces)]
+
+ suv = sinfo["uvs"]
+ spuv = sinfo["pin_uvs"]
+ ss = sinfo["seams"]
+ if len(sinfo["uvs"]) != len(dinfo["uvs"]):
+ ops_obj.report({'WARNING'}, "Some faces are different size")
+ return -1
+
+ suvs_fr = [uv for uv in suv]
+ spuvs_fr = [pin_uv for pin_uv in spuv]
+ ss_fr = [s for s in ss]
+
+ # flip UVs
+ if flip is True:
+ suvs_fr.reverse()
+ spuvs_fr.reverse()
+ ss_fr.reverse()
+
+ # rotate UVs
+ for _ in range(rotate):
+ uv = suvs_fr.pop()
+ pin_uv = spuvs_fr.pop()
+ s = ss_fr.pop()
+ suvs_fr.insert(0, uv)
+ spuvs_fr.insert(0, pin_uv)
+ ss_fr.insert(0, s)
+
+ # paste UVs
+ for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
+ spuvs_fr, ss_fr):
+ l[dlayer].uv = suv
+ l[dlayer].pin_uv = spuv
+ if copy_seams is True:
+ l.edge.seam = ss
+
+ return 0
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ src_info = None
+
+ scene.muv_props.copy_paste_uv = Props()
+ scene.muv_props.copy_paste_uv_selseq = Props()
+
+ scene.muv_copy_paste_uv_enabled = BoolProperty(
+ name="Copy/Paste UV Enabled",
+ description="Copy/Paste UV is enabled",
+ default=False
+ )
+ scene.muv_copy_paste_uv_copy_seams = BoolProperty(
+ name="Seams",
+ description="Copy Seams",
+ default=True
+ )
+ scene.muv_copy_paste_uv_mode = EnumProperty(
+ items=[
+ ('DEFAULT', "Default", "Default Mode"),
+ ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode")
+ ],
+ name="Copy/Paste UV Mode",
+ description="Copy/Paste UV Mode",
+ default='DEFAULT'
+ )
+ scene.muv_copy_paste_uv_strategy = EnumProperty(
+ name="Strategy",
+ description="Paste Strategy",
+ items=[
+ ('N_N', 'N:N', 'Number of faces must be equal to source'),
+ ('N_M', 'N:M', 'Number of faces must not be equal to source')
+ ],
+ default='N_M'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.copy_paste_uv
+ del scene.muv_props.copy_paste_uv_selseq
+ del scene.muv_copy_paste_uv_enabled
+ del scene.muv_copy_paste_uv_copy_seams
+ del scene.muv_copy_paste_uv_mode
+ del scene.muv_copy_paste_uv_strategy
+
+
+class OpeartorCopyUV(bpy.types.Operator):
"""
Operation class: Copy UV coordinate
"""
- bl_idname = "uv.muv_cpuv_copy_uv"
- bl_label = "Copy UV (Operation)"
- bl_description = "Copy UV coordinate (Operation)"
+ bl_idname = "uv.muv_copy_paste_uv_operator_copy_uv"
+ bl_label = "Copy UV"
+ bl_description = "Copy UV coordinate"
bl_options = {'REGISTER', 'UNDO'}
- uv_map = StringProperty(options={'HIDDEN'})
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
- props = context.scene.muv_props.cpuv
- if self.uv_map == "":
- self.report({'INFO'}, "Copy UV coordinate")
- else:
- self.report(
- {'INFO'}, "Copy UV coordinate (UV map:%s)" % (self.uv_map))
+ props = context.scene.muv_props.copy_paste_uv
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
+ bm = common.create_bmesh(obj)
# get UV layer
- if self.uv_map == "":
- if not bm.loops.layers.uv:
- self.report(
- {'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ uv_layers = get_copy_uv_layers(self, bm)
+ if not uv_layers:
+ return {'CANCELLED'}
# get selected face
- props.src_uvs = []
- props.src_pin_uvs = []
- props.src_seams = []
- for face in bm.faces:
- if face.select:
- uvs = [l[uv_layer].uv.copy() for l in face.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
- seams = [l.edge.seam for l in face.loops]
- props.src_uvs.append(uvs)
- props.src_pin_uvs.append(pin_uvs)
- props.src_seams.append(seams)
- if not props.src_uvs or not props.src_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
+ props.src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if face.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ "pin_uvs": [l[layer].pin_uv for l in face.loops],
+ "seams": [l.edge.seam for l in face.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ props.src_info[layer.name] = face_info
+
+ face_count = len([f for f in bm.faces if f.select])
+ self.report({'INFO'}, "{} face(s) are copied".format(face_count))
return {'FINISHED'}
-class MUV_CPUVCopyUVMenu(bpy.types.Menu):
+class MenuCopyUV(bpy.types.Menu):
"""
Menu class: Copy UV coordinate
"""
- bl_idname = "uv.muv_cpuv_copy_uv_menu"
- bl_label = "Copy UV"
- bl_description = "Copy UV coordinate"
+ bl_idname = "uv.muv_copy_paste_uv_menu_copy_uv"
+ bl_label = "Copy UV (Menu)"
+ bl_description = "Menu of Copy UV coordinate"
+
+ @classmethod
+ def poll(cls, context):
+ return is_valid_context(context)
def draw(self, context):
layout = self.layout
# create sub menu
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
+ bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
- layout.operator(
- MUV_CPUVCopyUV.bl_idname,
- text="[Default]",
- icon="IMAGE_COL"
- ).uv_map = ""
+
+ ops = layout.operator(OpeartorCopyUV.bl_idname, text="[Default]")
+ ops.uv_map = "__default"
+
+ ops = layout.operator(OpeartorCopyUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+
for m in uv_maps:
- layout.operator(
- MUV_CPUVCopyUV.bl_idname,
- text=m,
- icon="IMAGE_COL"
- ).uv_map = m
+ ops = layout.operator(OpeartorCopyUV.bl_idname, text=m)
+ ops.uv_map = m
-class MUV_CPUVPasteUV(bpy.types.Operator):
+class OperatorPasteUV(bpy.types.Operator):
"""
Operation class: Paste UV coordinate
"""
- bl_idname = "uv.muv_cpuv_paste_uv"
- bl_label = "Paste UV (Operation)"
- bl_description = "Paste UV coordinate (Operation)"
+ bl_idname = "uv.muv_copy_paste_uv_operator_paste_uv"
+ bl_label = "Paste UV"
+ bl_description = "Paste UV coordinate"
bl_options = {'REGISTER', 'UNDO'}
- uv_map = StringProperty(options={'HIDDEN'})
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
strategy = EnumProperty(
name="Strategy",
description="Paste Strategy",
@@ -153,104 +340,69 @@ class MUV_CPUVPasteUV(bpy.types.Operator):
max=30
)
copy_seams = BoolProperty(
- name="Copy Seams",
+ name="Seams",
description="Copy Seams",
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv
+ if not props.src_info:
+ return False
+ return is_valid_context(context)
+
def execute(self, context):
- props = context.scene.muv_props.cpuv
- if not props.src_uvs or not props.src_pin_uvs:
+ props = context.scene.muv_props.copy_paste_uv
+ if not props.src_info:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
- if self.uv_map == "":
- self.report({'INFO'}, "Paste UV coordinate")
- else:
- self.report(
- {'INFO'}, "Paste UV coordinate (UV map:%s)" % (self.uv_map))
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
+ bm = common.create_bmesh(obj)
# get UV layer
- if self.uv_map == "":
- if not bm.loops.layers.uv:
+ uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info)
+ if not uv_layers:
+ return {'CANCELLED'}
+
+ # get selected face
+ dest_face_count = 0
+ dest_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if face.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ key = list(props.src_info.keys())[0]
+ src_face_count = len(props.src_info[key])
+ dest_face_count = len(face_info)
+ if self.strategy == 'N_N' and src_face_count != dest_face_count:
self.report(
- {'WARNING'}, "Object must have more than one UV map")
+ {'WARNING'},
+ "Number of selected faces is different from copied" +
+ "(src:{}, dest:{})"
+ .format(src_face_count, dest_face_count))
return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ dest_info[layer.name] = face_info
- # get selected face
- dest_uvs = []
- dest_pin_uvs = []
- dest_seams = []
- dest_face_indices = []
- for face in bm.faces:
- if face.select:
- dest_face_indices.append(face.index)
- uvs = [l[uv_layer].uv.copy() for l in face.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
- seams = [l.edge.seam for l in face.loops]
- dest_uvs.append(uvs)
- dest_pin_uvs.append(pin_uvs)
- dest_seams.append(seams)
- if not dest_uvs or not dest_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs):
- self.report(
- {'WARNING'},
- "Number of selected faces is different from copied" +
- "(src:%d, dest:%d)" %
- (len(props.src_uvs), len(dest_uvs)))
+ # paste
+ ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+ self.strategy, self.flip_copied_uv,
+ self.rotate_copied_uv, self.copy_seams)
+ if ret:
return {'CANCELLED'}
- # paste
- for i, idx in enumerate(dest_face_indices):
- suv = None
- spuv = None
- ss = None
- duv = None
- if self.strategy == 'N_N':
- suv = props.src_uvs[i]
- spuv = props.src_pin_uvs[i]
- ss = props.src_seams[i]
- duv = dest_uvs[i]
- elif self.strategy == 'N_M':
- suv = props.src_uvs[i % len(props.src_uvs)]
- spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
- ss = props.src_seams[i % len(props.src_seams)]
- duv = dest_uvs[i]
- if len(suv) != len(duv):
- self.report({'WARNING'}, "Some faces are different size")
- return {'CANCELLED'}
- suvs_fr = [uv for uv in suv]
- spuvs_fr = [pin_uv for pin_uv in spuv]
- ss_fr = [s for s in ss]
- # flip UVs
- if self.flip_copied_uv is True:
- suvs_fr.reverse()
- spuvs_fr.reverse()
- ss_fr.reverse()
- # rotate UVs
- for _ in range(self.rotate_copied_uv):
- uv = suvs_fr.pop()
- pin_uv = spuvs_fr.pop()
- s = ss_fr.pop()
- suvs_fr.insert(0, uv)
- spuvs_fr.insert(0, pin_uv)
- ss_fr.insert(0, s)
- # paste UVs
- for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
- spuvs_fr, ss_fr):
- l[uv_layer].uv = suv
- l[uv_layer].pin_uv = spuv
- if self.copy_seams is True:
- l.edge.seam = ss
- self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
+ self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count))
bmesh.update_edit_mesh(obj.data)
if self.copy_seams is True:
@@ -259,234 +411,146 @@ class MUV_CPUVPasteUV(bpy.types.Operator):
return {'FINISHED'}
-class MUV_CPUVPasteUVMenu(bpy.types.Menu):
+class MenuPasteUV(bpy.types.Menu):
"""
Menu class: Paste UV coordinate
"""
- bl_idname = "uv.muv_cpuv_paste_uv_menu"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate"
+ bl_idname = "uv.muv_copy_paste_uv_menu_paste_uv"
+ bl_label = "Paste UV (Menu)"
+ bl_description = "Menu of Paste UV coordinate"
+
+ @classmethod
+ def poll(cls, context):
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv
+ if not props.src_info:
+ return False
+ return is_valid_context(context)
def draw(self, context):
sc = context.scene
layout = self.layout
# create sub menu
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
+ bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
- ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text="[Default]")
- ops.uv_map = ""
- ops.copy_seams = sc.muv_cpuv_copy_seams
- ops.strategy = sc.muv_cpuv_strategy
- for m in uv_maps:
- ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text=m)
- ops.uv_map = m
- ops.copy_seams = sc.muv_cpuv_copy_seams
- ops.strategy = sc.muv_cpuv_strategy
-
-class MUV_CPUVIECopyUV(bpy.types.Operator):
- """
- Operation class: Copy UV coordinate on UV/Image Editor
- """
-
- bl_idname = "uv.muv_cpuv_ie_copy_uv"
- bl_label = "Copy UV"
- bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
- bl_options = {'REGISTER', 'UNDO'}
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[Default]")
+ ops.uv_map = "__default"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
- @classmethod
- def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[New]")
+ ops.uv_map = "__new"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
- def execute(self, context):
- props = context.scene.muv_props.cpuv
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- uv_layer = bm.loops.layers.uv.verify()
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- for face in bm.faces:
- if not face.select:
- continue
- skip = False
- for l in face.loops:
- if not l[uv_layer].select:
- skip = True
- break
- if skip:
- continue
- props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops])
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
- return {'FINISHED'}
+ for m in uv_maps:
+ ops = layout.operator(OperatorPasteUV.bl_idname, text=m)
+ ops.uv_map = m
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
-class MUV_CPUVIEPasteUV(bpy.types.Operator):
+class OperatorSelSeqCopyUV(bpy.types.Operator):
"""
- Operation class: Paste UV coordinate on UV/Image Editor
+ Operation class: Copy UV coordinate by selection sequence
"""
- bl_idname = "uv.muv_cpuv_ie_paste_uv"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
+ bl_idname = "uv.muv_copy_paste_uv_operator_selseq_copy_uv"
+ bl_label = "Copy UV (Selection Sequence)"
+ bl_description = "Copy UV data by selection sequence"
bl_options = {'REGISTER', 'UNDO'}
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
-
- def execute(self, context):
- props = context.scene.muv_props.cpuv
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- uv_layer = bm.loops.layers.uv.verify()
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- dest_uvs = []
- dest_face_indices = []
- for face in bm.faces:
- if not face.select:
- continue
- skip = False
- for l in face.loops:
- if not l[uv_layer].select:
- skip = True
- break
- if skip:
- continue
- dest_face_indices.append(face.index)
- uvs = [l[uv_layer].uv.copy() for l in face.loops]
- dest_uvs.append(uvs)
-
- for suvs, duvs in zip(props.src_uvs, dest_uvs):
- src_diff = suvs[1] - suvs[0]
- dest_diff = duvs[1] - duvs[0]
-
- src_base = suvs[0]
- dest_base = duvs[0]
-
- src_rad = atan2(src_diff.y, src_diff.x)
- dest_rad = atan2(dest_diff.y, dest_diff.x)
- if src_rad < dest_rad:
- radian = dest_rad - src_rad
- elif src_rad > dest_rad:
- radian = math.pi * 2 - (src_rad - dest_rad)
- else: # src_rad == dest_rad
- radian = 0.0
-
- ratio = dest_diff.length / src_diff.length
- break
-
- for suvs, fidx in zip(props.src_uvs, dest_face_indices):
- for l, suv in zip(bm.faces[fidx].loops, suvs):
- base = suv - src_base
- radian_ref = atan2(base.y, base.x)
- radian_fin = (radian + radian_ref)
- length = base.length
- turn = Vector((length * cos(radian_fin),
- length * sin(radian_fin)))
- target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \
- dest_base
- l[uv_layer].uv = target_uv
-
- bmesh.update_edit_mesh(obj.data)
-
- return {'FINISHED'}
-
-
-class MUV_CPUVSelSeqCopyUV(bpy.types.Operator):
- """
- Operation class: Copy UV coordinate by selection sequence
- """
-
- bl_idname = "uv.muv_cpuv_selseq_copy_uv"
- bl_label = "Copy UV (Selection Sequence) (Operation)"
- bl_description = "Copy UV data by selection sequence (Operation)"
- bl_options = {'REGISTER', 'UNDO'}
-
- uv_map = StringProperty(options={'HIDDEN'})
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
- props = context.scene.muv_props.cpuv_selseq
- if self.uv_map == "":
- self.report({'INFO'}, "Copy UV coordinate (selection sequence)")
- else:
- self.report(
- {'INFO'},
- "Copy UV coordinate (selection sequence) (UV map:%s)"
- % (self.uv_map))
+ props = context.scene.muv_props.copy_paste_uv_selseq
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
+ bm = common.create_bmesh(obj)
# get UV layer
- if self.uv_map == "":
- if not bm.loops.layers.uv:
- self.report(
- {'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ uv_layers = get_copy_uv_layers(self, bm)
+ if not uv_layers:
+ return {'CANCELLED'}
# get selected face
- props.src_uvs = []
- props.src_pin_uvs = []
- props.src_seams = []
- for hist in bm.select_history:
- if isinstance(hist, bmesh.types.BMFace) and hist.select:
- uvs = [l[uv_layer].uv.copy() for l in hist.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
- seams = [l.edge.seam for l in hist.loops]
- props.src_uvs.append(uvs)
- props.src_pin_uvs.append(pin_uvs)
- props.src_seams.append(seams)
- if not props.src_uvs or not props.src_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs))
+ props.src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in hist.loops],
+ "pin_uvs": [l[layer].pin_uv for l in hist.loops],
+ "seams": [l.edge.seam for l in hist.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ props.src_info[layer.name] = face_info
+
+ face_count = len([f for f in bm.faces if f.select])
+ self.report({'INFO'}, "{} face(s) are selected".format(face_count))
return {'FINISHED'}
-class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu):
+class MenuSelSeqCopyUV(bpy.types.Menu):
"""
Menu class: Copy UV coordinate by selection sequence
"""
- bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu"
- bl_label = "Copy UV (Selection Sequence)"
- bl_description = "Copy UV coordinate by selection sequence"
+ bl_idname = "uv.muv_copy_paste_uv_menu_selseq_copy_uv"
+ bl_label = "Copy UV (Selection Sequence) (Menu)"
+ bl_description = "Menu of Copy UV coordinate by selection sequence"
+
+ @classmethod
+ def poll(cls, context):
+ return is_valid_context(context)
def draw(self, context):
layout = self.layout
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
+ bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
- layout.operator(
- MUV_CPUVSelSeqCopyUV.bl_idname,
- text="[Default]", icon="IMAGE_COL").uv_map = ""
+
+ ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[Default]")
+ ops.uv_map = "__default"
+
+ ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+
for m in uv_maps:
- layout.operator(
- MUV_CPUVSelSeqCopyUV.bl_idname,
- text=m, icon="IMAGE_COL").uv_map = m
+ ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text=m)
+ ops.uv_map = m
-class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
+class OperatorSelSeqPasteUV(bpy.types.Operator):
"""
Operation class: Paste UV coordinate by selection sequence
"""
- bl_idname = "uv.muv_cpuv_selseq_paste_uv"
- bl_label = "Paste UV (Selection Sequence) (Operation)"
- bl_description = "Paste UV coordinate by selection sequence (Operation)"
+ bl_idname = "uv.muv_copy_paste_uv_operator_selseq_paste_uv"
+ bl_label = "Paste UV (Selection Sequence)"
+ bl_description = "Paste UV coordinate by selection sequence"
bl_options = {'REGISTER', 'UNDO'}
- uv_map = StringProperty(options={'HIDDEN'})
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
strategy = EnumProperty(
name="Strategy",
description="Paste Strategy",
@@ -508,108 +572,69 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
max=30
)
copy_seams = BoolProperty(
- name="Copy Seams",
+ name="Seams",
description="Copy Seams",
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_selseq
+ if not props.src_info:
+ return False
+ return is_valid_context(context)
+
def execute(self, context):
- props = context.scene.muv_props.cpuv_selseq
- if not props.src_uvs or not props.src_pin_uvs:
+ props = context.scene.muv_props.copy_paste_uv_selseq
+ if not props.src_info:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
- if self.uv_map == "":
- self.report({'INFO'}, "Paste UV coordinate (selection sequence)")
- else:
- self.report(
- {'INFO'},
- "Paste UV coordinate (selection sequence) (UV map:%s)"
- % (self.uv_map))
-
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
+ bm = common.create_bmesh(obj)
# get UV layer
- if self.uv_map == "":
- if not bm.loops.layers.uv:
- self.report(
- {'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info)
+ if not uv_layers:
+ return {'CANCELLED'}
# get selected face
- dest_uvs = []
- dest_pin_uvs = []
- dest_seams = []
- dest_face_indices = []
- for hist in bm.select_history:
- if isinstance(hist, bmesh.types.BMFace) and hist.select:
- dest_face_indices.append(hist.index)
- uvs = [l[uv_layer].uv.copy() for l in hist.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in hist.loops]
- seams = [l.edge.seam for l in hist.loops]
- dest_uvs.append(uvs)
- dest_pin_uvs.append(pin_uvs)
- dest_seams.append(seams)
- if not dest_uvs or not dest_pin_uvs:
- self.report({'WARNING'}, "No faces are selected")
- return {'CANCELLED'}
- if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs):
- self.report(
- {'WARNING'},
- "Number of selected faces is different from copied faces " +
- "(src:%d, dest:%d)"
- % (len(props.src_uvs), len(dest_uvs)))
- return {'CANCELLED'}
+ dest_face_count = 0
+ dest_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for hist in bm.select_history:
+ if isinstance(hist, bmesh.types.BMFace) and hist.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in hist.loops],
+ }
+ face_info.append(info)
+ if not face_info:
+ self.report({'WARNING'}, "No faces are selected")
+ return {'CANCELLED'}
+ key = list(props.src_info.keys())[0]
+ src_face_count = len(props.src_info[key])
+ dest_face_count = len(face_info)
+ if self.strategy == 'N_N' and src_face_count != dest_face_count:
+ self.report(
+ {'WARNING'},
+ "Number of selected faces is different from copied" +
+ "(src:{}, dest:{})"
+ .format(src_face_count, dest_face_count))
+ return {'CANCELLED'}
+ dest_info[layer.name] = face_info
# paste
- for i, idx in enumerate(dest_face_indices):
- suv = None
- spuv = None
- ss = None
- duv = None
- if self.strategy == 'N_N':
- suv = props.src_uvs[i]
- spuv = props.src_pin_uvs[i]
- ss = props.src_seams[i]
- duv = dest_uvs[i]
- elif self.strategy == 'N_M':
- suv = props.src_uvs[i % len(props.src_uvs)]
- spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)]
- ss = props.src_seams[i % len(props.src_seams)]
- duv = dest_uvs[i]
- if len(suv) != len(duv):
- self.report({'WARNING'}, "Some faces are different size")
- return {'CANCELLED'}
- suvs_fr = [uv for uv in suv]
- spuvs_fr = [pin_uv for pin_uv in spuv]
- ss_fr = [s for s in ss]
- # flip UVs
- if self.flip_copied_uv is True:
- suvs_fr.reverse()
- spuvs_fr.reverse()
- ss_fr.reverse()
- # rotate UVs
- for _ in range(self.rotate_copied_uv):
- uv = suvs_fr.pop()
- pin_uv = spuvs_fr.pop()
- s = ss_fr.pop()
- suvs_fr.insert(0, uv)
- spuvs_fr.insert(0, pin_uv)
- ss_fr.insert(0, s)
- # paste UVs
- for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr,
- spuvs_fr, ss_fr):
- l[uv_layer].uv = suv
- l[uv_layer].pin_uv = spuv
- if self.copy_seams is True:
- l.edge.seam = ss
+ ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+ self.strategy, self.flip_copied_uv,
+ self.rotate_copied_uv, self.copy_seams)
+ if ret:
+ return {'CANCELLED'}
- self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs))
+ self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count))
bmesh.update_edit_mesh(obj.data)
if self.copy_seams is True:
@@ -618,29 +643,49 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator):
return {'FINISHED'}
-class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu):
+class MenuSelSeqPasteUV(bpy.types.Menu):
"""
Menu class: Paste UV coordinate by selection sequence
"""
- bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu"
- bl_label = "Paste UV (Selection Sequence)"
- bl_description = "Paste UV coordinate by selection sequence"
+ bl_idname = "uv.muv_copy_paste_uv_menu_selseq_paste_uv"
+ bl_label = "Paste UV (Selection Sequence) (Menu)"
+ bl_description = "Menu of Paste UV coordinate by selection sequence"
+
+ @classmethod
+ def poll(cls, context):
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_selseq
+ if not props.src_uvs or not props.src_pin_uvs:
+ return False
+ return is_valid_context(context)
def draw(self, context):
sc = context.scene
layout = self.layout
# create sub menu
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
+ bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
- ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname,
+
+ ops = layout.operator(OperatorSelSeqPasteUV.bl_idname,
text="[Default]")
- ops.uv_map = ""
- ops.copy_seams = sc.muv_cpuv_copy_seams
- ops.strategy = sc.muv_cpuv_strategy
+ ops.uv_map = "__default"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
+
+ ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[New]")
+ ops.uv_map = "__new"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
+
+ ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
+
for m in uv_maps:
- ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, text=m)
+ ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text=m)
ops.uv_map = m
- ops.copy_seams = sc.muv_cpuv_copy_seams
- ops.strategy = sc.muv_cpuv_strategy
+ ops.copy_seams = sc.muv_copy_paste_uv_copy_seams
+ ops.strategy = sc.muv_copy_paste_uv_strategy
diff --git a/uv_magic_uv/op/copy_paste_uv_object.py b/uv_magic_uv/op/copy_paste_uv_object.py
index d80ee415..eb6d87c9 100644
--- a/uv_magic_uv/op/copy_paste_uv_object.py
+++ b/uv_magic_uv/op/copy_paste_uv_object.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
@@ -31,6 +31,41 @@ from bpy.props import (
)
from .. import common
+from .copy_paste_uv import (
+ get_copy_uv_layers,
+ get_paste_uv_layers,
+ paste_uv
+)
+
+
+__all__ = [
+ 'Properties',
+ 'OperatorCopyUV',
+ 'MenuCopyUV',
+ 'OperatorPasteUV',
+ 'MenuPasteUV',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only object mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'OBJECT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
def memorize_view_3d_mode(fn):
@@ -42,101 +77,138 @@ def memorize_view_3d_mode(fn):
return __memorize_view_3d_mode
-class MUV_CPUVObjCopyUV(bpy.types.Operator):
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ src_info = None
+
+ scene.muv_props.copy_paste_uv_object = Props()
+
+ scene.muv_copy_paste_uv_object_copy_seams = BoolProperty(
+ name="Seams",
+ description="Copy Seams",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.copy_paste_uv_object
+ del scene.muv_copy_paste_uv_object_copy_seams
+
+
+class OperatorCopyUV(bpy.types.Operator):
"""
- Operation class: Copy UV coordinate per object
+ Operation class: Copy UV coordinate among objects
"""
- bl_idname = "object.muv_cpuv_obj_copy_uv"
- bl_label = "Copy UV"
- bl_description = "Copy UV coordinate"
+ bl_idname = "object.muv_copy_paste_uv_object_operator_copy_uv"
+ bl_label = "Copy UV (Among Objects)"
+ bl_description = "Copy UV coordinate (Among Objects)"
bl_options = {'REGISTER', 'UNDO'}
- uv_map = StringProperty(options={'HIDDEN'})
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
@memorize_view_3d_mode
def execute(self, context):
- props = context.scene.muv_props.cpuv_obj
- if self.uv_map == "":
- self.report({'INFO'}, "Copy UV coordinate per object")
- else:
- self.report(
- {'INFO'},
- "Copy UV coordinate per object (UV map:%s)" % (self.uv_map))
+ props = context.scene.muv_props.copy_paste_uv_object
bpy.ops.object.mode_set(mode='EDIT')
-
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
+ bm = common.create_bmesh(obj)
# get UV layer
- if self.uv_map == "":
- if not bm.loops.layers.uv:
- self.report(
- {'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ uv_layers = get_copy_uv_layers(self, bm)
+ if not uv_layers:
+ return {'CANCELLED'}
# get selected face
- props.src_uvs = []
- props.src_pin_uvs = []
- props.src_seams = []
- for face in bm.faces:
- uvs = [l[uv_layer].uv.copy() for l in face.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
- seams = [l.edge.seam for l in face.loops]
- props.src_uvs.append(uvs)
- props.src_pin_uvs.append(pin_uvs)
- props.src_seams.append(seams)
-
- self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name))
+ props.src_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if face.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ "pin_uvs": [l[layer].pin_uv for l in face.loops],
+ "seams": [l.edge.seam for l in face.loops],
+ }
+ face_info.append(info)
+ props.src_info[layer.name] = face_info
+
+ self.report({'INFO'},
+ "{}'s UV coordinates are copied".format(obj.name))
return {'FINISHED'}
-class MUV_CPUVObjCopyUVMenu(bpy.types.Menu):
+class MenuCopyUV(bpy.types.Menu):
"""
- Menu class: Copy UV coordinate per object
+ Menu class: Copy UV coordinate among objects
"""
- bl_idname = "object.muv_cpuv_obj_copy_uv_menu"
- bl_label = "Copy UV"
- bl_description = "Copy UV coordinate per object"
+ bl_idname = "object.muv_copy_paste_uv_object_menu_copy_uv"
+ bl_label = "Copy UV (Among Objects) (Menu)"
+ bl_description = "Menu of Copy UV coordinate (Among Objects)"
+
+ @classmethod
+ def poll(cls, context):
+ return is_valid_context(context)
def draw(self, _):
layout = self.layout
# create sub menu
uv_maps = bpy.context.active_object.data.uv_textures.keys()
- layout.operator(MUV_CPUVObjCopyUV.bl_idname, text="[Default]")\
- .uv_map = ""
+
+ ops = layout.operator(OperatorCopyUV.bl_idname, text="[Default]")
+ ops.uv_map = "__default"
+
+ ops = layout.operator(OperatorCopyUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+
for m in uv_maps:
- layout.operator(MUV_CPUVObjCopyUV.bl_idname, text=m).uv_map = m
+ ops = layout.operator(OperatorCopyUV.bl_idname, text=m)
+ ops.uv_map = m
-class MUV_CPUVObjPasteUV(bpy.types.Operator):
+class OperatorPasteUV(bpy.types.Operator):
"""
- Operation class: Paste UV coordinate per object
+ Operation class: Paste UV coordinate among objects
"""
- bl_idname = "object.muv_cpuv_obj_paste_uv"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate"
+ bl_idname = "object.muv_copy_paste_uv_object_operator_paste_uv"
+ bl_label = "Paste UV (Among Objects)"
+ bl_description = "Paste UV coordinate (Among Objects)"
bl_options = {'REGISTER', 'UNDO'}
- uv_map = StringProperty(options={'HIDDEN'})
+ uv_map = StringProperty(default="__default", options={'HIDDEN'})
copy_seams = BoolProperty(
- name="Copy Seams",
+ name="Seams",
description="Copy Seams",
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_object
+ if not props.src_info:
+ return False
+ return is_valid_context(context)
+
@memorize_view_3d_mode
def execute(self, context):
- props = context.scene.muv_props.cpuv_obj
- if not props.src_uvs or not props.src_pin_uvs:
+ props = context.scene.muv_props.copy_paste_uv_object
+ if not props.src_info:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
@@ -149,90 +221,67 @@ class MUV_CPUVObjPasteUV(bpy.types.Operator):
bpy.ops.object.mode_set(mode='EDIT')
obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- if (self.uv_map == "" or
- self.uv_map not in bm.loops.layers.uv.keys()):
- self.report({'INFO'}, "Paste UV coordinate per object")
- else:
- self.report(
- {'INFO'},
- "Paste UV coordinate per object (UV map: %s)"
- % (self.uv_map))
+ bm = common.create_bmesh(obj)
# get UV layer
- if (self.uv_map == "" or
- self.uv_map not in bm.loops.layers.uv.keys()):
- if not bm.loops.layers.uv:
- self.report(
- {'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
- else:
- uv_layer = bm.loops.layers.uv[self.uv_map]
+ uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info)
+ if not uv_layers:
+ return {'CANCELLED'}
# get selected face
- dest_uvs = []
- dest_pin_uvs = []
- dest_seams = []
- dest_face_indices = []
- for face in bm.faces:
- dest_face_indices.append(face.index)
- uvs = [l[uv_layer].uv.copy() for l in face.loops]
- pin_uvs = [l[uv_layer].pin_uv for l in face.loops]
- seams = [l.edge.seam for l in face.loops]
- dest_uvs.append(uvs)
- dest_pin_uvs.append(pin_uvs)
- dest_seams.append(seams)
- if len(props.src_uvs) != len(dest_uvs):
- self.report(
- {'WARNING'},
- "Number of faces is different from copied " +
- "(src:%d, dest:%d)"
- % (len(props.src_uvs), len(dest_uvs))
- )
- return {'CANCELLED'}
+ dest_info = {}
+ for layer in uv_layers:
+ face_info = []
+ for face in bm.faces:
+ if face.select:
+ info = {
+ "uvs": [l[layer].uv.copy() for l in face.loops],
+ }
+ face_info.append(info)
+ key = list(props.src_info.keys())[0]
+ src_face_count = len(props.src_info[key])
+ dest_face_count = len(face_info)
+ if src_face_count != dest_face_count:
+ self.report(
+ {'WARNING'},
+ "Number of selected faces is different from copied" +
+ "(src:{}, dest:{})"
+ .format(src_face_count, dest_face_count))
+ return {'CANCELLED'}
+ dest_info[layer.name] = face_info
# paste
- for i, idx in enumerate(dest_face_indices):
- suv = props.src_uvs[i]
- spuv = props.src_pin_uvs[i]
- ss = props.src_seams[i]
- duv = dest_uvs[i]
- if len(suv) != len(duv):
- self.report({'WARNING'}, "Some faces are different size")
- return {'CANCELLED'}
- suvs_fr = [uv for uv in suv]
- spuvs_fr = [pin_uv for pin_uv in spuv]
- ss_fr = [s for s in ss]
- # paste UVs
- for l, suv, spuv, ss in zip(
- bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr):
- l[uv_layer].uv = suv
- l[uv_layer].pin_uv = spuv
- if self.copy_seams is True:
- l.edge.seam = ss
+ ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers,
+ 'N_N', 0, 0, self.copy_seams)
+ if ret:
+ return {'CANCELLED'}
bmesh.update_edit_mesh(obj.data)
if self.copy_seams is True:
obj.data.show_edge_seams = True
self.report(
- {'INFO'}, "%s's UV coordinates are pasted" % (obj.name))
+ {'INFO'}, "{}'s UV coordinates are pasted".format(obj.name))
return {'FINISHED'}
-class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
+class MenuPasteUV(bpy.types.Menu):
"""
- Menu class: Paste UV coordinate per object
+ Menu class: Paste UV coordinate among objects
"""
- bl_idname = "object.muv_cpuv_obj_paste_uv_menu"
- bl_label = "Paste UV"
- bl_description = "Paste UV coordinate per object"
+ bl_idname = "object.muv_copy_paste_uv_object_menu_paste_uv"
+ bl_label = "Paste UV (Among Objects) (Menu)"
+ bl_description = "Menu of Paste UV coordinate (Among Objects)"
+
+ @classmethod
+ def poll(cls, context):
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_object
+ if not props.src_info:
+ return False
+ return is_valid_context(context)
def draw(self, context):
sc = context.scene
@@ -242,11 +291,20 @@ class MUV_CPUVObjPasteUVMenu(bpy.types.Menu):
for obj in bpy.data.objects:
if hasattr(obj.data, "uv_textures") and obj.select:
uv_maps.extend(obj.data.uv_textures.keys())
- uv_maps = list(set(uv_maps))
- ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text="[Default]")
- ops.uv_map = ""
- ops.copy_seams = sc.muv_cpuv_copy_seams
+
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[Default]")
+ ops.uv_map = "__default"
+ ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[New]")
+ ops.uv_map = "__new"
+ ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
+ ops = layout.operator(OperatorPasteUV.bl_idname, text="[All]")
+ ops.uv_map = "__all"
+ ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
+
for m in uv_maps:
- ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text=m)
+ ops = layout.operator(OperatorPasteUV.bl_idname, text=m)
ops.uv_map = m
- ops.copy_seams = sc.muv_cpuv_copy_seams
+ ops.copy_seams = sc.muv_copy_paste_uv_object_copy_seams
diff --git a/uv_magic_uv/op/copy_paste_uv_uvedit.py b/uv_magic_uv/op/copy_paste_uv_uvedit.py
index 96908020..e591b5f1 100644
--- a/uv_magic_uv/op/copy_paste_uv_uvedit.py
+++ b/uv_magic_uv/op/copy_paste_uv_uvedit.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import math
from math import atan2, sin, cos
@@ -33,28 +33,75 @@ from mathutils import Vector
from .. import common
-class MUV_CPUVIECopyUV(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'OperatorCopyUV',
+ 'OperatorPasteUV',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ src_uvs = None
+
+ scene.muv_props.copy_paste_uv_uvedit = Props()
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.copy_paste_uv_uvedit
+
+
+class OperatorCopyUV(bpy.types.Operator):
"""
Operation class: Copy UV coordinate on UV/Image Editor
"""
- bl_idname = "uv.muv_cpuv_ie_copy_uv"
- bl_label = "Copy UV"
+ bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_copy_uv"
+ bl_label = "Copy UV (UV/Image Editor)"
bl_description = "Copy UV coordinate (only selected in UV/Image Editor)"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
- props = context.scene.muv_props.cpuv
+ props = context.scene.muv_props.copy_paste_uv_uvedit
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
uv_layer = bm.loops.layers.uv.verify()
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
+ props.src_uvs = []
for face in bm.faces:
if not face.select:
continue
@@ -70,22 +117,29 @@ class MUV_CPUVIECopyUV(bpy.types.Operator):
return {'FINISHED'}
-class MUV_CPUVIEPasteUV(bpy.types.Operator):
+class OperatorPasteUV(bpy.types.Operator):
"""
Operation class: Paste UV coordinate on UV/Image Editor
"""
- bl_idname = "uv.muv_cpuv_ie_paste_uv"
- bl_label = "Paste UV"
+ bl_idname = "uv.muv_copy_paste_uv_uvedit_operator_paste_uv"
+ bl_label = "Paste UV (UV/Image Editor)"
bl_description = "Paste UV coordinate (only selected in UV/Image Editor)"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_uvedit
+ if not props.src_uvs:
+ return False
+ return is_valid_context(context)
def execute(self, context):
- props = context.scene.muv_props.cpuv
+ props = context.scene.muv_props.copy_paste_uv_uvedit
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
uv_layer = bm.loops.layers.uv.verify()
diff --git a/uv_magic_uv/op/flip_rotate_uv.py b/uv_magic_uv/op/flip_rotate_uv.py
index 30f6b0f7..751bb8fb 100644
--- a/uv_magic_uv/op/flip_rotate_uv.py
+++ b/uv_magic_uv/op/flip_rotate_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
@@ -33,12 +33,59 @@ from bpy.props import (
from .. import common
-class MUV_FlipRot(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_flip_rotate_uv_enabled = BoolProperty(
+ name="Flip/Rotate UV Enabled",
+ description="Flip/Rotate UV is enabled",
+ default=False
+ )
+ scene.muv_flip_rotate_uv_seams = BoolProperty(
+ name="Seams",
+ description="Seams",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_flip_rotate_uv_enabled
+ del scene.muv_flip_rotate_uv_seams
+
+
+class Operator(bpy.types.Operator):
"""
Operation class: Flip and Rotate UV coordinate
"""
- bl_idname = "uv.muv_fliprot"
+ bl_idname = "uv.muv_flip_rotate_uv_operator"
bl_label = "Flip/Rotate UV"
bl_description = "Flip/Rotate UV coordinate"
bl_options = {'REGISTER', 'UNDO'}
@@ -60,6 +107,13 @@ class MUV_FlipRot(bpy.types.Operator):
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
self.report({'INFO'}, "Flip/Rotate UV")
obj = context.active_object
diff --git a/uv_magic_uv/op/mirror_uv.py b/uv_magic_uv/op/mirror_uv.py
index f4849d18..11ad2bca 100644
--- a/uv_magic_uv/op/mirror_uv.py
+++ b/uv_magic_uv/op/mirror_uv.py
@@ -20,13 +20,14 @@
__author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
from bpy.props import (
EnumProperty,
FloatProperty,
+ BoolProperty,
)
import bmesh
from mathutils import Vector
@@ -34,12 +35,64 @@ from mathutils import Vector
from .. import common
-class MUV_MirrorUV(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_mirror_uv_enabled = BoolProperty(
+ name="Mirror UV Enabled",
+ description="Mirror UV is enabled",
+ default=False
+ )
+ scene.muv_mirror_uv_axis = EnumProperty(
+ items=[
+ ('X', "X", "Mirror Along X axis"),
+ ('Y', "Y", "Mirror Along Y axis"),
+ ('Z', "Z", "Mirror Along Z axis")
+ ],
+ name="Axis",
+ description="Mirror Axis",
+ default='X'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_mirror_uv_enabled
+ del scene.muv_mirror_uv_axis
+
+
+class Operator(bpy.types.Operator):
"""
Operation class: Mirror UV
"""
- bl_idname = "uv.muv_mirror_uv"
+ bl_idname = "uv.muv_mirror_uv_operator"
bl_label = "Mirror UV"
bl_options = {'REGISTER', 'UNDO'}
@@ -104,8 +157,10 @@ class MUV_MirrorUV(bpy.types.Operator):
@classmethod
def poll(cls, context):
- obj = context.active_object
- return obj and obj.type == 'MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
obj = context.active_object
diff --git a/uv_magic_uv/op/move_uv.py b/uv_magic_uv/op/move_uv.py
index 6382376c..a229ae34 100644
--- a/uv_magic_uv/op/move_uv.py
+++ b/uv_magic_uv/op/move_uv.py
@@ -20,23 +20,68 @@
__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
from mathutils import Vector
+from bpy.props import BoolProperty
+from .. import common
-class MUV_MVUV(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_move_uv_enabled = BoolProperty(
+ name="Move UV Enabled",
+ description="Move UV is enabled",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_move_uv_enabled
+
+
+class Operator(bpy.types.Operator):
"""
- Operator class: Move UV from View3D
+ Operator class: Move UV
"""
- bl_idname = "view3d.muv_mvuv"
- bl_label = "Move the UV from View3D"
+ bl_idname = "uv.muv_move_uv_operator"
+ bl_label = "Move UV"
bl_options = {'REGISTER', 'UNDO'}
+ __running = False
+
def __init__(self):
self.__topology_dict = []
self.__prev_mouse = Vector((0.0, 0.0))
@@ -44,7 +89,20 @@ class MUV_MVUV(bpy.types.Operator):
self.__prev_offset_uv = Vector((0.0, 0.0))
self.__first_time = True
self.__ini_uvs = []
- self.__running = False
+ self.__operating = False
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ if cls.is_running(context):
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return cls.__running
def __find_uv(self, context):
bm = bmesh.from_edit_mesh(context.object.data)
@@ -59,12 +117,7 @@ class MUV_MVUV(bpy.types.Operator):
return topology_dict, uvs
- @classmethod
- def poll(cls, context):
- return context.edit_object
-
def modal(self, context, event):
- props = context.scene.muv_props.mvuv
if self.__first_time is True:
self.__prev_mouse = Vector((
event.mouse_region_x, event.mouse_region_y))
@@ -85,9 +138,9 @@ class MUV_MVUV(bpy.types.Operator):
event.mouse_region_x, event.mouse_region_y))
# check if operation is started
- if self.__running:
+ if not self.__operating:
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
- self.__running = False
+ self.__operating = True
return {'RUNNING_MODAL'}
# update UV
@@ -111,20 +164,24 @@ class MUV_MVUV(bpy.types.Operator):
if event.type == cancel_btn and event.value == 'PRESS':
for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
bm.faces[fidx].loops[vidx][active_uv].uv = uv
- props.running = False
+ Operator.__running = False
return {'FINISHED'}
# confirmed
if event.type == confirm_btn and event.value == 'PRESS':
- props.running = False
+ Operator.__running = False
return {'FINISHED'}
return {'RUNNING_MODAL'}
def execute(self, context):
- props = context.scene.muv_props.mvuv
- props.running = True
- self.__running = True
+ Operator.__running = True
+ self.__operating = False
self.__first_time = True
+
context.window_manager.modal_handler_add(self)
self.__topology_dict, self.__ini_uvs = self.__find_uv(context)
+
+ if context.area:
+ context.area.tag_redraw()
+
return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/op/pack_uv.py b/uv_magic_uv/op/pack_uv.py
index a780af3e..39340fda 100644
--- a/uv_magic_uv/op/pack_uv.py
+++ b/uv_magic_uv/op/pack_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from math import fabs
@@ -38,7 +38,68 @@ from mathutils import Vector
from .. import common
-class MUV_PackUV(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_pack_uv_enabled = BoolProperty(
+ name="Pack UV Enabled",
+ description="Pack UV is enabled",
+ default=False
+ )
+ scene.muv_pack_uv_allowable_center_deviation = FloatVectorProperty(
+ name="Allowable Center Deviation",
+ description="Allowable center deviation to judge same UV island",
+ min=0.000001,
+ max=0.1,
+ default=(0.001, 0.001),
+ size=2
+ )
+ scene.muv_pack_uv_allowable_size_deviation = FloatVectorProperty(
+ name="Allowable Size Deviation",
+ description="Allowable sizse deviation to judge same UV island",
+ min=0.000001,
+ max=0.1,
+ default=(0.001, 0.001),
+ size=2
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_pack_uv_enabled
+ del scene.muv_pack_uv_allowable_center_deviation
+ del scene.muv_pack_uv_allowable_size_deviation
+
+
+class Operator(bpy.types.Operator):
"""
Operation class: Pack UV with same UV islands are integrated
Island matching algorithm
@@ -47,7 +108,7 @@ class MUV_PackUV(bpy.types.Operator):
- Same number of UV
"""
- bl_idname = "uv.muv_packuv"
+ bl_idname = "uv.muv_pack_uv_operator"
bl_label = "Pack UV"
bl_description = "Pack UV (Same UV Islands are integrated)"
bl_options = {'REGISTER', 'UNDO'}
@@ -79,6 +140,13 @@ class MUV_PackUV(bpy.types.Operator):
size=2
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
diff --git a/uv_magic_uv/op/preserve_uv_aspect.py b/uv_magic_uv/op/preserve_uv_aspect.py
index bc2f1b81..cb11bd45 100644
--- a/uv_magic_uv/op/preserve_uv_aspect.py
+++ b/uv_magic_uv/op/preserve_uv_aspect.py
@@ -20,23 +20,93 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
-from bpy.props import StringProperty, EnumProperty
+from bpy.props import StringProperty, EnumProperty, BoolProperty
from mathutils import Vector
from .. import common
-class MUV_PreserveUVAspect(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ def get_loaded_texture_name(_, __):
+ items = [(key, key, "") for key in bpy.data.images.keys()]
+ items.append(("None", "None", ""))
+ return items
+
+ scene.muv_preserve_uv_aspect_enabled = BoolProperty(
+ name="Preserve UV Aspect Enabled",
+ description="Preserve UV Aspect is enabled",
+ default=False
+ )
+ scene.muv_preserve_uv_aspect_tex_image = EnumProperty(
+ name="Image",
+ description="Texture Image",
+ items=get_loaded_texture_name
+ )
+ scene.muv_preserve_uv_aspect_origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', 'Center', 'Center'),
+ ('LEFT_TOP', 'Left Top', 'Left Bottom'),
+ ('LEFT_CENTER', 'Left Center', 'Left Center'),
+ ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
+ ('CENTER_TOP', 'Center Top', 'Center Top'),
+ ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
+ ('RIGHT_TOP', 'Right Top', 'Right Top'),
+ ('RIGHT_CENTER', 'Right Center', 'Right Center'),
+ ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
+
+ ],
+ default="CENTER"
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_preserve_uv_aspect_enabled
+ del scene.muv_preserve_uv_aspect_tex_image
+ del scene.muv_preserve_uv_aspect_origin
+
+
+class Operator(bpy.types.Operator):
"""
Operation class: Preserve UV Aspect
"""
- bl_idname = "uv.muv_preserve_uv_aspect"
+ bl_idname = "uv.muv_preserve_uv_aspect_operator"
bl_label = "Preserve UV Aspect"
bl_description = "Choose Image"
bl_options = {'REGISTER', 'UNDO'}
@@ -62,8 +132,10 @@ class MUV_PreserveUVAspect(bpy.types.Operator):
@classmethod
def poll(cls, context):
- obj = context.active_object
- return obj and obj.type == 'MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
# Note: the current system only works if the
diff --git a/uv_magic_uv/op/select_uv.py b/uv_magic_uv/op/select_uv.py
new file mode 100644
index 00000000..3a7bcbc3
--- /dev/null
+++ b/uv_magic_uv/op/select_uv.py
@@ -0,0 +1,161 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
+
+import bpy
+import bmesh
+from bpy.props import BoolProperty
+
+from .. import common
+
+
+__all__ = [
+ 'Properties',
+ 'OperatorSelectFlipped',
+ 'OperatorSelectOverlapped',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_select_uv_enabled = BoolProperty(
+ name="Select UV Enabled",
+ description="Select UV is enabled",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_select_uv_enabled
+
+
+class OperatorSelectOverlapped(bpy.types.Operator):
+ """
+ Operation class: Select faces which have overlapped UVs
+ """
+
+ bl_idname = "uv.muv_select_uv_operator_select_overlapped"
+ bl_label = "Overlapped"
+ bl_description = "Select faces which have overlapped UVs"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+
+ overlapped_info = common.get_overlapped_uv_info(bm, sel_faces,
+ uv_layer, 'FACE')
+
+ for info in overlapped_info:
+ if context.tool_settings.use_uv_select_sync:
+ info["subject_face"].select = True
+ else:
+ for l in info["subject_face"].loops:
+ l[uv_layer].select = True
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+class OperatorSelectFlipped(bpy.types.Operator):
+ """
+ Operation class: Select faces which have flipped UVs
+ """
+
+ bl_idname = "uv.muv_select_uv_operator_select_flipped"
+ bl_label = "Flipped"
+ bl_description = "Select faces which have flipped UVs"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def execute(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+
+ flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
+
+ for info in flipped_info:
+ if context.tool_settings.use_uv_select_sync:
+ info["face"].select = True
+ else:
+ for l in info["face"].loops:
+ l[uv_layer].select = True
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
diff --git a/uv_magic_uv/op/smooth_uv.py b/uv_magic_uv/op/smooth_uv.py
index aa9b22c0..31bef155 100644
--- a/uv_magic_uv/op/smooth_uv.py
+++ b/uv_magic_uv/op/smooth_uv.py
@@ -20,8 +20,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
@@ -30,9 +30,72 @@ from bpy.props import BoolProperty, FloatProperty
from .. import common
-class MUV_AUVSmooth(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
- bl_idname = "uv.muv_auv_smooth"
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_smooth_uv_enabled = BoolProperty(
+ name="Smooth UV Enabled",
+ description="Smooth UV is enabled",
+ default=False
+ )
+ scene.muv_smooth_uv_transmission = BoolProperty(
+ name="Transmission",
+ description="Smooth linked UVs",
+ default=False
+ )
+ scene.muv_smooth_uv_mesh_infl = FloatProperty(
+ name="Mesh Influence",
+ description="Influence rate of mesh vertex",
+ min=0.0,
+ max=1.0,
+ default=0.0
+ )
+ scene.muv_smooth_uv_select = BoolProperty(
+ name="Select",
+ description="Select UVs which are smoothed",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_smooth_uv_enabled
+ del scene.muv_smooth_uv_transmission
+ del scene.muv_smooth_uv_mesh_infl
+ del scene.muv_smooth_uv_select
+
+
+class Operator(bpy.types.Operator):
+
+ bl_idname = "uv.muv_smooth_uv_operator"
bl_label = "Smooth"
bl_description = "Smooth UV coordinates"
bl_options = {'REGISTER', 'UNDO'}
@@ -57,7 +120,10 @@ class MUV_AUVSmooth(bpy.types.Operator):
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def __smooth_wo_transmission(self, loop_seqs, uv_layer):
# calculate path length
diff --git a/uv_magic_uv/op/texture_lock.py b/uv_magic_uv/op/texture_lock.py
index d6c56f5a..4be97c62 100644
--- a/uv_magic_uv/op/texture_lock.py
+++ b/uv_magic_uv/op/texture_lock.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import math
from math import atan2, cos, sqrt, sin, fabs
@@ -34,6 +34,14 @@ from bpy.props import BoolProperty
from .. import common
+__all__ = [
+ 'Properties',
+ 'OperatorLock',
+ 'OperatorUnlock',
+ 'OperatorIntr',
+]
+
+
def get_vco(verts_orig, loop):
"""
Get vertex original coordinate from loop
@@ -169,8 +177,13 @@ def calc_tri_vert(v0, v1, angle0, angle1):
xd = 0
yd = 0
else:
- xd = (b * b - a * a + d * d) / (2 * d)
- yd = 2 * sqrt(s * (s - a) * (s - b) * (s - d)) / d
+ r = s * (s - a) * (s - b) * (s - d)
+ if r < 0:
+ xd = 0
+ yd = 0
+ else:
+ xd = (b * b - a * a + d * d) / (2 * d)
+ yd = 2 * sqrt(r) / d
x1 = xd * cos(alpha) - yd * sin(alpha) + v0.x
y1 = xd * sin(alpha) + yd * cos(alpha) + v0.y
x2 = xd * cos(alpha) + yd * sin(alpha) + v0.x
@@ -179,18 +192,97 @@ def calc_tri_vert(v0, v1, angle0, angle1):
return Vector((x1, y1)), Vector((x2, y2))
-class MUV_TexLockStart(bpy.types.Operator):
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ verts_orig = None
+
+ scene.muv_props.texture_lock = Props()
+
+ def get_func(_):
+ return OperatorIntr.is_running(bpy.context)
+
+ def set_func(_, __):
+ pass
+
+ def update_func(_, __):
+ bpy.ops.uv.muv_texture_lock_operator_intr('INVOKE_REGION_WIN')
+
+ scene.muv_texture_lock_enabled = BoolProperty(
+ name="Texture Lock Enabled",
+ description="Texture Lock is enabled",
+ default=False
+ )
+ scene.muv_texture_lock_lock = BoolProperty(
+ name="Texture Lock Locked",
+ description="Texture Lock is locked",
+ default=False,
+ get=get_func,
+ set=set_func,
+ update=update_func
+ )
+ scene.muv_texture_lock_connect = BoolProperty(
+ name="Connect UV",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.texture_lock
+ del scene.muv_texture_lock_enabled
+ del scene.muv_texture_lock_lock
+ del scene.muv_texture_lock_connect
+
+
+class OperatorLock(bpy.types.Operator):
"""
- Operation class: Start Texture Lock
+ Operation class: Lock Texture
"""
- bl_idname = "uv.muv_texlock_start"
- bl_label = "Start"
- bl_description = "Start Texture Lock"
+ bl_idname = "uv.muv_texture_lock_operator_lock"
+ bl_label = "Lock Texture"
+ bl_description = "Lock Texture"
bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ @classmethod
+ def is_ready(cls, context):
+ sc = context.scene
+ props = sc.muv_props.texture_lock
+ if props.verts_orig:
+ return True
+ return False
+
def execute(self, context):
- props = context.scene.muv_props.texlock
+ props = context.scene.muv_props.texture_lock
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -210,14 +302,14 @@ class MUV_TexLockStart(bpy.types.Operator):
return {'FINISHED'}
-class MUV_TexLockStop(bpy.types.Operator):
+class OperatorUnlock(bpy.types.Operator):
"""
- Operation class: Stop Texture Lock
+ Operation class: Unlock Texture
"""
- bl_idname = "uv.muv_texlock_stop"
- bl_label = "Stop"
- bl_description = "Stop Texture Lock"
+ bl_idname = "uv.muv_texture_lock_operator_unlock"
+ bl_label = "Unlock Texture"
+ bl_description = "Unlock Texture"
bl_options = {'REGISTER', 'UNDO'}
connect = BoolProperty(
@@ -225,9 +317,20 @@ class MUV_TexLockStop(bpy.types.Operator):
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.texture_lock
+ if not props.verts_orig:
+ return False
+ return OperatorLock.is_ready(context) and is_valid_context(context)
+
def execute(self, context):
sc = context.scene
- props = sc.muv_props.texlock
+ props = sc.muv_props.texture_lock
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -275,27 +378,81 @@ class MUV_TexLockStop(bpy.types.Operator):
v_orig["moved"] = True
bmesh.update_edit_mesh(obj.data)
+ props.verts_orig = None
+
return {'FINISHED'}
-class MUV_TexLockUpdater(bpy.types.Operator):
+class OperatorIntr(bpy.types.Operator):
"""
- Operation class: Texture locking updater
+ Operation class: Texture Lock (Interactive mode)
"""
- bl_idname = "uv.muv_texlock_updater"
- bl_label = "Texture Lock Updater"
- bl_description = "Texture Lock Updater"
+ bl_idname = "uv.muv_texture_lock_operator_intr"
+ bl_label = "Texture Lock (Interactive mode)"
+ bl_description = "Internal operation for Texture Lock (Interactive mode)"
+
+ __timer = None
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return 1 if cls.__timer else 0
+
+ @classmethod
+ def handle_add(cls, self_, context):
+ if cls.__timer is None:
+ cls.__timer = context.window_manager.event_timer_add(
+ 0.10, context.window)
+ context.window_manager.modal_handler_add(self_)
+
+ @classmethod
+ def handle_remove(cls, context):
+ if cls.__timer is not None:
+ context.window_manager.event_timer_remove(cls.__timer)
+ cls.__timer = None
def __init__(self):
- self.__timer = None
+ self.__intr_verts_orig = []
+ self.__intr_verts = []
+
+ def __sel_verts_changed(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ prev = set(self.__intr_verts)
+ now = set([v.index for v in bm.verts if v.select])
+
+ return prev != now
+
+ def __reinit_verts(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ self.__intr_verts_orig = [
+ {"vidx": v.index, "vco": v.co.copy(), "moved": False}
+ for v in bm.verts if v.select]
+ self.__intr_verts = [v.index for v in bm.verts if v.select]
def __update_uv(self, context):
"""
Update UV when vertex coordinates are changed
"""
- props = context.scene.muv_props.texlock
- obj = bpy.context.active_object
+ obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.verts.ensure_lookup_table()
@@ -308,7 +465,7 @@ class MUV_TexLockUpdater(bpy.types.Operator):
uv_layer = bm.loops.layers.uv.verify()
verts = [v.index for v in bm.verts if v.select]
- verts_orig = props.intr_verts_orig
+ verts_orig = self.__intr_verts_orig
for vidx, v_orig in zip(verts, verts_orig):
if vidx != v_orig["vidx"]:
@@ -337,98 +494,40 @@ class MUV_TexLockUpdater(bpy.types.Operator):
bmesh.update_edit_mesh(obj.data)
common.redraw_all_areas()
- props.intr_verts_orig = [
+ self.__intr_verts_orig = [
{"vidx": v.index, "vco": v.co.copy(), "moved": False}
for v in bm.verts if v.select]
def modal(self, context, event):
- props = context.scene.muv_props.texlock
- if context.area:
- context.area.tag_redraw()
- if props.intr_running is False:
- self.__handle_remove(context)
+ if not is_valid_context(context):
+ OperatorIntr.handle_remove(context)
return {'FINISHED'}
- if event.type == 'TIMER':
- self.__update_uv(context)
-
- return {'PASS_THROUGH'}
-
- def __handle_add(self, context):
- if self.__timer is None:
- self.__timer = context.window_manager.event_timer_add(
- 0.10, context.window)
- context.window_manager.modal_handler_add(self)
- def __handle_remove(self, context):
- if self.__timer is not None:
- context.window_manager.event_timer_remove(self.__timer)
- self.__timer = None
+ if not OperatorIntr.is_running(context):
+ return {'FINISHED'}
- def execute(self, context):
- props = context.scene.muv_props.texlock
- if props.intr_running is False:
- self.__handle_add(context)
- props.intr_running = True
- return {'RUNNING_MODAL'}
- else:
- props.intr_running = False
if context.area:
context.area.tag_redraw()
- return {'FINISHED'}
-
-
-class MUV_TexLockIntrStart(bpy.types.Operator):
- """
- Operation class: Start texture locking (Interactive mode)
- """
-
- bl_idname = "uv.muv_texlock_intr_start"
- bl_label = "Texture Lock Start (Interactive mode)"
- bl_description = "Texture Lock Start (Realtime UV update)"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- props = context.scene.muv_props.texlock
- if props.intr_running is True:
- return {'CANCELLED'}
+ if event.type == 'TIMER':
+ if self.__sel_verts_changed(context):
+ self.__reinit_verts(context)
+ else:
+ self.__update_uv(context)
- obj = bpy.context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
+ return {'PASS_THROUGH'}
- if not bm.loops.layers.uv:
- self.report({'WARNING'}, "Object must have more than one UV map")
+ def invoke(self, context, _):
+ if not is_valid_context(context):
return {'CANCELLED'}
- props.intr_verts_orig = [
- {"vidx": v.index, "vco": v.co.copy(), "moved": False}
- for v in bm.verts if v.select]
-
- bpy.ops.uv.muv_texlock_updater()
-
- return {'FINISHED'}
-
-
-# Texture lock (Stop, Interactive mode)
-class MUV_TexLockIntrStop(bpy.types.Operator):
- """
- Operation class: Stop texture locking (interactive mode)
- """
-
- bl_idname = "uv.muv_texlock_intr_stop"
- bl_label = "Texture Lock Stop (Interactive mode)"
- bl_description = "Texture Lock Stop (Realtime UV update)"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- props = context.scene.muv_props.texlock
- if props.intr_running is False:
- return {'CANCELLED'}
+ if not OperatorIntr.is_running(context):
+ OperatorIntr.handle_add(self, context)
+ return {'RUNNING_MODAL'}
+ else:
+ OperatorIntr.handle_remove(context)
- bpy.ops.uv.muv_texlock_updater()
+ if context.area:
+ context.area.tag_redraw()
return {'FINISHED'}
diff --git a/uv_magic_uv/op/texture_projection.py b/uv_magic_uv/op/texture_projection.py
index 77a81aa0..bdf0ad67 100644
--- a/uv_magic_uv/op/texture_projection.py
+++ b/uv_magic_uv/op/texture_projection.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from collections import namedtuple
@@ -30,14 +30,32 @@ import bgl
import bmesh
import mathutils
from bpy_extras import view3d_utils
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+)
from .. import common
+__all__ = [
+ 'Properties',
+ 'Operator',
+ 'OperatorProject',
+]
+
+
Rect = namedtuple('Rect', 'x0 y0 x1 y1')
Rect2 = namedtuple('Rect2', 'x y width height')
+def get_loaded_texture_name(_, __):
+ items = [(key, key, "") for key in bpy.data.images.keys()]
+ items.append(("None", "None", ""))
+ return items
+
+
def get_canvas(context, magnitude):
"""
Get canvas to be renderred texture
@@ -47,20 +65,20 @@ def get_canvas(context, magnitude):
region_w = context.region.width
region_h = context.region.height
- canvas_w = region_w - prefs.texproj_canvas_padding[0] * 2.0
- canvas_h = region_h - prefs.texproj_canvas_padding[1] * 2.0
+ canvas_w = region_w - prefs.texture_projection_canvas_padding[0] * 2.0
+ canvas_h = region_h - prefs.texture_projection_canvas_padding[1] * 2.0
- img = bpy.data.images[sc.muv_texproj_tex_image]
+ img = bpy.data.images[sc.muv_texture_projection_tex_image]
tex_w = img.size[0]
tex_h = img.size[1]
center_x = region_w * 0.5
center_y = region_h * 0.5
- if sc.muv_texproj_adjust_window:
+ if sc.muv_texture_projection_adjust_window:
ratio_x = canvas_w / tex_w
ratio_y = canvas_h / tex_h
- if sc.muv_texproj_apply_tex_aspect:
+ if sc.muv_texture_projection_apply_tex_aspect:
ratio = ratio_y if ratio_x > ratio_y else ratio_x
len_x = ratio * tex_w
len_y = ratio * tex_h
@@ -68,7 +86,7 @@ def get_canvas(context, magnitude):
len_x = canvas_w
len_y = canvas_h
else:
- if sc.muv_texproj_apply_tex_aspect:
+ if sc.muv_texture_projection_apply_tex_aspect:
len_x = tex_w * magnitude
len_y = tex_h * magnitude
else:
@@ -104,44 +122,149 @@ def region_to_canvas(rg_vec, canvas):
return cv_vec
-class MUV_TexProjRenderer(bpy.types.Operator):
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ def get_func(_):
+ return Operator.is_running(bpy.context)
+
+ def set_func(_, __):
+ pass
+
+ def update_func(_, __):
+ bpy.ops.uv.muv_texture_projection_operator('INVOKE_REGION_WIN')
+
+ scene.muv_texture_projection_enabled = BoolProperty(
+ name="Texture Projection Enabled",
+ description="Texture Projection is enabled",
+ default=False
+ )
+ scene.muv_texture_projection_enable = BoolProperty(
+ name="Texture Projection Enabled",
+ description="Texture Projection is enabled",
+ default=False,
+ get=get_func,
+ set=set_func,
+ update=update_func
+ )
+ scene.muv_texture_projection_tex_magnitude = FloatProperty(
+ name="Magnitude",
+ description="Texture Magnitude",
+ default=0.5,
+ min=0.0,
+ max=100.0
+ )
+ scene.muv_texture_projection_tex_image = EnumProperty(
+ name="Image",
+ description="Texture Image",
+ items=get_loaded_texture_name
+ )
+ scene.muv_texture_projection_tex_transparency = FloatProperty(
+ name="Transparency",
+ description="Texture Transparency",
+ default=0.2,
+ min=0.0,
+ max=1.0
+ )
+ scene.muv_texture_projection_adjust_window = BoolProperty(
+ name="Adjust Window",
+ description="Size of renderered texture is fitted to window",
+ default=True
+ )
+ scene.muv_texture_projection_apply_tex_aspect = BoolProperty(
+ name="Texture Aspect Ratio",
+ description="Apply Texture Aspect ratio to displayed texture",
+ default=True
+ )
+ scene.muv_texture_projection_assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_texture_projection_enabled
+ del scene.muv_texture_projection_tex_magnitude
+ del scene.muv_texture_projection_tex_image
+ del scene.muv_texture_projection_tex_transparency
+ del scene.muv_texture_projection_adjust_window
+ del scene.muv_texture_projection_apply_tex_aspect
+ del scene.muv_texture_projection_assign_uvmap
+
+
+class Operator(bpy.types.Operator):
"""
- Operation class: Render selected texture
- No operation (only rendering texture)
+ Operation class: Texture Projection
+ Render texture
"""
- bl_idname = "uv.muv_texproj_renderer"
+ bl_idname = "uv.muv_texture_projection_operator"
bl_description = "Render selected texture"
bl_label = "Texture renderer"
__handle = None
- @staticmethod
- def handle_add(obj, context):
- MUV_TexProjRenderer.__handle = bpy.types.SpaceView3D.draw_handler_add(
- MUV_TexProjRenderer.draw_texture,
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return 1 if cls.__handle else 0
+
+ @classmethod
+ def handle_add(cls, obj, context):
+ cls.__handle = bpy.types.SpaceView3D.draw_handler_add(
+ Operator.draw_texture,
(obj, context), 'WINDOW', 'POST_PIXEL')
- @staticmethod
- def handle_remove():
- if MUV_TexProjRenderer.__handle is not None:
- bpy.types.SpaceView3D.draw_handler_remove(
- MUV_TexProjRenderer.__handle, 'WINDOW')
- MUV_TexProjRenderer.__handle = None
+ @classmethod
+ def handle_remove(cls):
+ if cls.__handle is not None:
+ bpy.types.SpaceView3D.draw_handler_remove(cls.__handle, 'WINDOW')
+ cls.__handle = None
- @staticmethod
- def draw_texture(_, context):
+ @classmethod
+ def draw_texture(cls, _, context):
sc = context.scene
+ if not cls.is_running(context):
+ return
+
# no textures are selected
- if sc.muv_texproj_tex_image == "None":
+ if sc.muv_texture_projection_tex_image == "None":
return
# get texture to be renderred
- img = bpy.data.images[sc.muv_texproj_tex_image]
+ img = bpy.data.images[sc.muv_texture_projection_tex_image]
# setup rendering region
- rect = get_canvas(context, sc.muv_texproj_tex_magnitude)
+ rect = get_canvas(context, sc.muv_texture_projection_tex_magnitude)
positions = [
[rect.x0, rect.y0],
[rect.x0, rect.y1],
@@ -170,74 +293,48 @@ class MUV_TexProjRenderer(bpy.types.Operator):
# render texture
bgl.glBegin(bgl.GL_QUADS)
- bgl.glColor4f(1.0, 1.0, 1.0, sc.muv_texproj_tex_transparency)
+ bgl.glColor4f(1.0, 1.0, 1.0,
+ sc.muv_texture_projection_tex_transparency)
for (v1, v2), (u, v) in zip(positions, tex_coords):
bgl.glTexCoord2f(u, v)
bgl.glVertex2f(v1, v2)
bgl.glEnd()
+ def invoke(self, context, _):
+ if not Operator.is_running(context):
+ Operator.handle_add(self, context)
+ else:
+ Operator.handle_remove()
-class MUV_TexProjStart(bpy.types.Operator):
- """
- Operation class: Start Texture Projection
- """
-
- bl_idname = "uv.muv_texproj_start"
- bl_label = "Start Texture Projection"
- bl_description = "Start Texture Projection"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- props = context.scene.muv_props.texproj
- if props.running is False:
- MUV_TexProjRenderer.handle_add(self, context)
- props.running = True
- if context.area:
- context.area.tag_redraw()
-
- return {'FINISHED'}
-
-
-class MUV_TexProjStop(bpy.types.Operator):
- """
- Operation class: Stop Texture Projection
- """
-
- bl_idname = "uv.muv_texproj_stop"
- bl_label = "Stop Texture Projection"
- bl_description = "Stop Texture Projection"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- props = context.scene.muv_props.texproj
- if props.running is True:
- MUV_TexProjRenderer.handle_remove()
- props.running = False
if context.area:
context.area.tag_redraw()
return {'FINISHED'}
-class MUV_TexProjProject(bpy.types.Operator):
+class OperatorProject(bpy.types.Operator):
"""
Operation class: Project texture
"""
- bl_idname = "uv.muv_texproj_project"
+ bl_idname = "uv.muv_texture_projection_operator_project"
bl_label = "Project Texture"
bl_description = "Project Texture"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
- obj = context.active_object
- return obj is not None and obj.type == "MESH"
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ if not Operator.is_running(context):
+ return False
+ return is_valid_context(context)
def execute(self, context):
sc = context.scene
- if sc.muv_texproj_tex_image == "None":
+ if sc.muv_texture_projection_tex_image == "None":
self.report({'WARNING'}, "No textures are selected")
return {'CANCELLED'}
@@ -253,7 +350,7 @@ class MUV_TexProjProject(bpy.types.Operator):
# get UV and texture layer
if not bm.loops.layers.uv:
- if sc.muv_texproj_assign_uvmap:
+ if sc.muv_texture_projection_assign_uvmap:
bm.loops.layers.uv.new()
else:
self.report({'WARNING'},
@@ -278,14 +375,16 @@ class MUV_TexProjProject(bpy.types.Operator):
v_canvas = [
region_to_canvas(
v,
- get_canvas(bpy.context, sc.muv_texproj_tex_magnitude))
- for v in v_screen
+ get_canvas(bpy.context,
+ sc.muv_texture_projection_tex_magnitude)
+ ) for v in v_screen
]
# project texture to object
i = 0
for f in sel_faces:
- f[tex_layer].image = bpy.data.images[sc.muv_texproj_tex_image]
+ f[tex_layer].image = \
+ bpy.data.images[sc.muv_texture_projection_tex_image]
for l in f.loops:
l[uv_layer].uv = v_canvas[i].to_2d()
i = i + 1
diff --git a/uv_magic_uv/op/texture_wrap.py b/uv_magic_uv/op/texture_wrap.py
index 04669214..a7c58847 100644
--- a/uv_magic_uv/op/texture_wrap.py
+++ b/uv_magic_uv/op/texture_wrap.py
@@ -20,27 +20,98 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
+from bpy.props import (
+ BoolProperty,
+)
from .. import common
-class MUV_TexWrapRefer(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'OperatorRefer',
+ 'OperatorSet',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ ref_face_index = -1
+ ref_obj = None
+
+ scene.muv_props.texture_wrap = Props()
+
+ scene.muv_texture_wrap_enabled = BoolProperty(
+ name="Texture Wrap",
+ description="Texture Wrap is enabled",
+ default=False
+ )
+ scene.muv_texture_wrap_set_and_refer = BoolProperty(
+ name="Set and Refer",
+ description="Refer and set UV",
+ default=True
+ )
+ scene.muv_texture_wrap_selseq = BoolProperty(
+ name="Selection Sequence",
+ description="Set UV sequentially",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.texture_wrap
+ del scene.muv_texture_wrap_enabled
+ del scene.muv_texture_wrap_set_and_refer
+ del scene.muv_texture_wrap_selseq
+
+
+class OperatorRefer(bpy.types.Operator):
"""
Operation class: Refer UV
"""
- bl_idname = "uv.muv_texwrap_refer"
+ bl_idname = "uv.muv_texture_wrap_operator_refer"
bl_label = "Refer"
bl_description = "Refer UV"
bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
- props = context.scene.muv_props.texwrap
+ props = context.scene.muv_props.texture_wrap
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -61,19 +132,30 @@ class MUV_TexWrapRefer(bpy.types.Operator):
return {'FINISHED'}
-class MUV_TexWrapSet(bpy.types.Operator):
+class OperatorSet(bpy.types.Operator):
"""
Operation class: Set UV
"""
- bl_idname = "uv.muv_texwrap_set"
+ bl_idname = "uv.muv_texture_wrap_operator_set"
bl_label = "Set"
bl_description = "Set UV"
bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.texture_wrap
+ if not props.ref_obj:
+ return False
+ return is_valid_context(context)
+
def execute(self, context):
sc = context.scene
- props = sc.muv_props.texwrap
+ props = sc.muv_props.texture_wrap
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -84,7 +166,7 @@ class MUV_TexWrapSet(bpy.types.Operator):
return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
- if sc.muv_texwrap_selseq:
+ if sc.muv_texture_wrap_selseq:
sel_faces = []
for hist in bm.select_history:
if isinstance(hist, bmesh.types.BMFace) and hist.select:
@@ -206,7 +288,7 @@ class MUV_TexWrapSet(bpy.types.Operator):
ref_face_index = tgt_face_index
- if sc.muv_texwrap_set_and_refer:
+ if sc.muv_texture_wrap_set_and_refer:
props.ref_face_index = tgt_face_index
return {'FINISHED'}
diff --git a/uv_magic_uv/op/transfer_uv.py b/uv_magic_uv/op/transfer_uv.py
index 132f395e..ef6fc3be 100644
--- a/uv_magic_uv/op/transfer_uv.py
+++ b/uv_magic_uv/op/transfer_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from collections import OrderedDict
@@ -32,19 +32,84 @@ from bpy.props import BoolProperty
from .. import common
-class MUV_TransUVCopy(bpy.types.Operator):
+__all__ = [
+ 'OperatorCopyUV',
+ 'OperatorPasteUV',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ topology_copied = None
+
+ scene.muv_props.transfer_uv = Props()
+
+ scene.muv_transfer_uv_enabled = BoolProperty(
+ name="Transfer UV Enabled",
+ description="Transfer UV is enabled",
+ default=False
+ )
+ scene.muv_transfer_uv_invert_normals = BoolProperty(
+ name="Invert Normals",
+ description="Invert Normals",
+ default=False
+ )
+ scene.muv_transfer_uv_copy_seams = BoolProperty(
+ name="Copy Seams",
+ description="Copy Seams",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_transfer_uv_enabled
+ del scene.muv_transfer_uv_invert_normals
+ del scene.muv_transfer_uv_copy_seams
+
+
+class OperatorCopyUV(bpy.types.Operator):
"""
Operation class: Transfer UV copy
Topological based copy
"""
- bl_idname = "uv.muv_transuv_copy"
- bl_label = "Transfer UV Copy"
- bl_description = "Transfer UV Copy (Topological based copy)"
+ bl_idname = "uv.muv_transfer_uv_operator_copy_uv"
+ bl_label = "Transfer UV Copy UV"
+ bl_description = "Transfer UV Copy UV (Topological based copy)"
bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
- props = context.scene.muv_props.transuv
+ props = context.scene.muv_props.transfer_uv
active_obj = context.scene.objects.active
bm = bmesh.from_edit_mesh(active_obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -56,7 +121,7 @@ class MUV_TransUVCopy(bpy.types.Operator):
return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
- props.topology_copied.clear()
+ props.topology_copied = []
# get selected faces
active_face = bm.faces.active
@@ -82,21 +147,23 @@ class MUV_TransUVCopy(bpy.types.Operator):
pin_uvs = [l.pin_uv for l in uv_loops]
seams = [e.seam for e in edges]
props.topology_copied.append([uvs, pin_uvs, seams])
+ else:
+ return {'CANCELLED'}
bmesh.update_edit_mesh(active_obj.data)
return {'FINISHED'}
-class MUV_TransUVPaste(bpy.types.Operator):
+class OperatorPasteUV(bpy.types.Operator):
"""
Operation class: Transfer UV paste
Topological based paste
"""
- bl_idname = "uv.muv_transuv_paste"
- bl_label = "Transfer UV Paste"
- bl_description = "Transfer UV Paste (Topological based paste)"
+ bl_idname = "uv.muv_transfer_uv_operator_paste_uv"
+ bl_label = "Transfer UV Paste UV"
+ bl_description = "Transfer UV Paste UV (Topological based paste)"
bl_options = {'REGISTER', 'UNDO'}
invert_normals = BoolProperty(
@@ -110,8 +177,19 @@ class MUV_TransUVPaste(bpy.types.Operator):
default=True
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ sc = context.scene
+ props = sc.muv_props.transfer_uv
+ if not props.topology_copied:
+ return False
+ return is_valid_context(context)
+
def execute(self, context):
- props = context.scene.muv_props.transuv
+ props = context.scene.muv_props.transfer_uv
active_obj = context.scene.objects.active
bm = bmesh.from_edit_mesh(active_obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -153,7 +231,7 @@ class MUV_TransUVPaste(bpy.types.Operator):
{'WARNING'},
"Mesh has different amount of faces"
)
- return {'FINISHED'}
+ return {'CANCELLED'}
for j, face_data in enumerate(all_sorted_faces.values()):
copied_data = props.topology_copied[j]
@@ -175,6 +253,8 @@ class MUV_TransUVPaste(bpy.types.Operator):
uvloop.pin_uv = copied_data[1][k]
if self.copy_seams:
edge.seam = copied_data[2][k]
+ else:
+ return {'CANCELLED'}
bmesh.update_edit_mesh(active_obj.data)
if self.copy_seams:
@@ -302,7 +382,7 @@ def parse_faces(
used_verts.update(shared_face.verts)
used_edges.update(shared_face.edges)
- if common.DEBUG:
+ if common.is_debug_mode():
shared_face.select = True # test which faces are parsed
new_shared_faces.append(shared_face)
diff --git a/uv_magic_uv/op/unwrap_constraint.py b/uv_magic_uv/op/unwrap_constraint.py
index e98879b7..b2368fc4 100644
--- a/uv_magic_uv/op/unwrap_constraint.py
+++ b/uv_magic_uv/op/unwrap_constraint.py
@@ -18,8 +18,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
@@ -32,12 +32,65 @@ from bpy.props import (
from .. import common
-class MUV_UnwrapConstraint(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_unwrap_constraint_enabled = BoolProperty(
+ name="Unwrap Constraint Enabled",
+ description="Unwrap Constraint is enabled",
+ default=False
+ )
+ scene.muv_unwrap_constraint_u_const = BoolProperty(
+ name="U-Constraint",
+ description="Keep UV U-axis coordinate",
+ default=False
+ )
+ scene.muv_unwrap_constraint_v_const = BoolProperty(
+ name="V-Constraint",
+ description="Keep UV V-axis coordinate",
+ default=False
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_unwrap_constraint_enabled
+ del scene.muv_unwrap_constraint_u_const
+ del scene.muv_unwrap_constraint_v_const
+
+
+class Operator(bpy.types.Operator):
"""
Operation class: Unwrap with constrain UV coordinate
"""
- bl_idname = "uv.muv_unwrap_constraint"
+ bl_idname = "uv.muv_unwrap_constraint_operator"
bl_label = "Unwrap Constraint"
bl_description = "Unwrap while keeping uv coordinate"
bl_options = {'REGISTER', 'UNDO'}
@@ -83,6 +136,13 @@ class MUV_UnwrapConstraint(bpy.types.Operator):
default=False
)
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, _):
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data)
diff --git a/uv_magic_uv/op/uv_bounding_box.py b/uv_magic_uv/op/uv_bounding_box.py
index 9ebc76c4..4aa8874b 100644
--- a/uv_magic_uv/op/uv_bounding_box.py
+++ b/uv_magic_uv/op/uv_bounding_box.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from enum import IntEnum
import math
@@ -30,14 +30,101 @@ import bpy
import bgl
import mathutils
import bmesh
+from bpy.props import BoolProperty, EnumProperty
from .. import common
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
MAX_VALUE = 100000.0
-class MUV_UVBBCmd():
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ uv_info_ini = []
+ ctrl_points_ini = []
+ ctrl_points = []
+
+ scene.muv_props.uv_bounding_box = Props()
+
+ def get_func(_):
+ return Operator.is_running(bpy.context)
+
+ def set_func(_, __):
+ pass
+
+ def update_func(_, __):
+ bpy.ops.uv.muv_uv_bounding_box_operator('INVOKE_REGION_WIN')
+
+ scene.muv_uv_bounding_box_enabled = BoolProperty(
+ name="UV Bounding Box Enabled",
+ description="UV Bounding Box is enabled",
+ default=False
+ )
+ scene.muv_uv_bounding_box_show = BoolProperty(
+ name="UV Bounding Box Showed",
+ description="UV Bounding Box is showed",
+ default=False,
+ get=get_func,
+ set=set_func,
+ update=update_func
+ )
+ scene.muv_uv_bounding_box_uniform_scaling = BoolProperty(
+ name="Uniform Scaling",
+ description="Enable Uniform Scaling",
+ default=False
+ )
+ scene.muv_uv_bounding_box_boundary = EnumProperty(
+ name="Boundary",
+ description="Boundary",
+ default='UV_SEL',
+ items=[
+ ('UV', "UV", "Boundary is decided by UV"),
+ ('UV_SEL', "UV (Selected)",
+ "Boundary is decided by Selected UV")
+ ]
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.uv_bounding_box
+ del scene.muv_uv_bounding_box_enabled
+ del scene.muv_uv_bounding_box_show
+ del scene.muv_uv_bounding_box_uniform_scaling
+ del scene.muv_uv_bounding_box_boundary
+
+
+class CommandBase():
"""
Custom class: Base class of command
"""
@@ -52,7 +139,7 @@ class MUV_UVBBCmd():
return mat
-class MUV_UVBBTranslationCmd(MUV_UVBBCmd):
+class TranslationCommand(CommandBase):
"""
Custom class: Translation operation
"""
@@ -76,7 +163,7 @@ class MUV_UVBBTranslationCmd(MUV_UVBBCmd):
self.__y = y
-class MUV_UVBBRotationCmd(MUV_UVBBCmd):
+class RotationCommand(CommandBase):
"""
Custom class: Rotation operation
"""
@@ -107,7 +194,7 @@ class MUV_UVBBRotationCmd(MUV_UVBBCmd):
self.__y = y
-class MUV_UVBBScalingCmd(MUV_UVBBCmd):
+class ScalingCommand(CommandBase):
"""
Custom class: Scaling operation
"""
@@ -158,7 +245,7 @@ class MUV_UVBBScalingCmd(MUV_UVBBCmd):
self.__y = y
-class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd):
+class UniformScalingCommand(CommandBase):
"""
Custom class: Uniform Scaling operation
"""
@@ -222,7 +309,7 @@ class MUV_UVBBUniformScalingCmd(MUV_UVBBCmd):
self.__y = y
-class MUV_UVBBCmdExecuter():
+class CommandExecuter():
"""
Custom class: manage command history and execute command
"""
@@ -288,67 +375,7 @@ class MUV_UVBBCmdExecuter():
self.__cmd_list.append(cmd)
-class MUV_UVBBRenderer(bpy.types.Operator):
- """
- Operation class: Render UV bounding box
- """
-
- bl_idname = "uv.muv_uvbb_renderer"
- bl_label = "UV Bounding Box Renderer"
- bl_description = "Bounding Box Renderer about UV in Image Editor"
-
- __handle = None
-
- @staticmethod
- def handle_add(obj, context):
- if MUV_UVBBRenderer.__handle is None:
- sie = bpy.types.SpaceImageEditor
- MUV_UVBBRenderer.__handle = sie.draw_handler_add(
- MUV_UVBBRenderer.draw_bb,
- (obj, context), "WINDOW", "POST_PIXEL")
-
- @staticmethod
- def handle_remove():
- if MUV_UVBBRenderer.__handle is not None:
- sie = bpy.types.SpaceImageEditor
- sie.draw_handler_remove(
- MUV_UVBBRenderer.__handle, "WINDOW")
- MUV_UVBBRenderer.__handle = None
-
- @staticmethod
- def __draw_ctrl_point(context, pos):
- """
- Draw control point
- """
- prefs = context.user_preferences.addons["uv_magic_uv"].preferences
- cp_size = prefs.uvbb_cp_size
- offset = cp_size / 2
- verts = [
- [pos.x - offset, pos.y - offset],
- [pos.x - offset, pos.y + offset],
- [pos.x + offset, pos.y + offset],
- [pos.x + offset, pos.y - offset]
- ]
- bgl.glEnable(bgl.GL_BLEND)
- bgl.glBegin(bgl.GL_QUADS)
- bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
- for (x, y) in verts:
- bgl.glVertex2f(x, y)
- bgl.glEnd()
-
- @staticmethod
- def draw_bb(_, context):
- """
- Draw bounding box
- """
- props = context.scene.muv_props.uvbb
- for cp in props.ctrl_points:
- MUV_UVBBRenderer.__draw_ctrl_point(
- context, mathutils.Vector(
- context.region.view2d.view_to_region(cp.x, cp.y)))
-
-
-class MUV_UVBBState(IntEnum):
+class State(IntEnum):
"""
Enum: State definition used by MUV_UVBBStateMgr
"""
@@ -369,7 +396,7 @@ class MUV_UVBBState(IntEnum):
UNIFORM_SCALING_4 = 14
-class MUV_UVBBStateBase():
+class StateBase():
"""
Custom class: Base class of state
"""
@@ -381,7 +408,7 @@ class MUV_UVBBStateBase():
raise NotImplementedError
-class MUV_UVBBStateNone(MUV_UVBBStateBase):
+class StateNone(StateBase):
"""
Custom class:
No state
@@ -397,8 +424,8 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase):
Update state
"""
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
- cp_react_size = prefs.uvbb_cp_react_size
- is_uscaling = context.scene.muv_uvbb_uniform_scaling
+ cp_react_size = prefs.uv_bounding_box_cp_react_size
+ is_uscaling = context.scene.muv_uv_bounding_box_uniform_scaling
if (event.type == 'LEFTMOUSE') and (event.value == 'PRESS'):
x, y = context.region.view2d.view_to_region(
mouse_view.x, mouse_view.y)
@@ -413,16 +440,16 @@ class MUV_UVBBStateNone(MUV_UVBBStateBase):
arr = [1, 3, 6, 8]
if i in arr:
return (
- MUV_UVBBState.UNIFORM_SCALING_1 +
+ State.UNIFORM_SCALING_1 +
arr.index(i)
)
else:
- return MUV_UVBBState.TRANSLATING + i
+ return State.TRANSLATING + i
- return MUV_UVBBState.NONE
+ return State.NONE
-class MUV_UVBBStateTranslating(MUV_UVBBStateBase):
+class StateTranslating(StateBase):
"""
Custom class: Translating state
"""
@@ -431,19 +458,19 @@ class MUV_UVBBStateTranslating(MUV_UVBBStateBase):
super().__init__()
self.__cmd_exec = cmd_exec
ix, iy = ctrl_points[0].x, ctrl_points[0].y
- self.__cmd_exec.append(MUV_UVBBTranslationCmd(ix, iy))
+ self.__cmd_exec.append(TranslationCommand(ix, iy))
def update(self, context, event, ctrl_points, mouse_view):
if event.type == 'LEFTMOUSE':
if event.value == 'RELEASE':
- return MUV_UVBBState.NONE
+ return State.NONE
if event.type == 'MOUSEMOVE':
x, y = mouse_view.x, mouse_view.y
self.__cmd_exec.top().set(x, y)
- return MUV_UVBBState.TRANSLATING
+ return State.TRANSLATING
-class MUV_UVBBStateScaling(MUV_UVBBStateBase):
+class StateScaling(StateBase):
"""
Custom class: Scaling state
"""
@@ -460,19 +487,19 @@ class MUV_UVBBStateScaling(MUV_UVBBStateBase):
dir_x, dir_y = dir_x_list[idx], dir_y_list[idx]
mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
self.__cmd_exec.append(
- MUV_UVBBScalingCmd(ix, iy, ox, oy, dir_x, dir_y, mat.inverted()))
+ ScalingCommand(ix, iy, ox, oy, dir_x, dir_y, mat.inverted()))
def update(self, context, event, ctrl_points, mouse_view):
if event.type == 'LEFTMOUSE':
if event.value == 'RELEASE':
- return MUV_UVBBState.NONE
+ return State.NONE
if event.type == 'MOUSEMOVE':
x, y = mouse_view.x, mouse_view.y
self.__cmd_exec.top().set(x, y)
return self.__state
-class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
+class StateUniformScaling(StateBase):
"""
Custom class: Uniform Scaling state
"""
@@ -483,17 +510,17 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
self.__cmd_exec = cmd_exec
icp_idx = [1, 3, 6, 8]
ocp_idx = [8, 6, 3, 1]
- idx = state - MUV_UVBBState.UNIFORM_SCALING_1
+ idx = state - State.UNIFORM_SCALING_1
ix, iy = ctrl_points[icp_idx[idx]].x, ctrl_points[icp_idx[idx]].y
ox, oy = ctrl_points[ocp_idx[idx]].x, ctrl_points[ocp_idx[idx]].y
mat = self.__cmd_exec.execute(end=self.__cmd_exec.undo_size())
- self.__cmd_exec.append(MUV_UVBBUniformScalingCmd(
+ self.__cmd_exec.append(UniformScalingCommand(
ix, iy, ox, oy, mat.inverted()))
def update(self, context, event, ctrl_points, mouse_view):
if event.type == 'LEFTMOUSE':
if event.value == 'RELEASE':
- return MUV_UVBBState.NONE
+ return State.NONE
if event.type == 'MOUSEMOVE':
x, y = mouse_view.x, mouse_view.y
self.__cmd_exec.top().set(x, y)
@@ -501,7 +528,7 @@ class MUV_UVBBStateUniformScaling(MUV_UVBBStateBase):
return self.__state
-class MUV_UVBBStateRotating(MUV_UVBBStateBase):
+class StateRotating(StateBase):
"""
Custom class: Rotating state
"""
@@ -511,27 +538,27 @@ class MUV_UVBBStateRotating(MUV_UVBBStateBase):
self.__cmd_exec = cmd_exec
ix, iy = ctrl_points[9].x, ctrl_points[9].y
ox, oy = ctrl_points[0].x, ctrl_points[0].y
- self.__cmd_exec.append(MUV_UVBBRotationCmd(ix, iy, ox, oy))
+ self.__cmd_exec.append(RotationCommand(ix, iy, ox, oy))
def update(self, context, event, ctrl_points, mouse_view):
if event.type == 'LEFTMOUSE':
if event.value == 'RELEASE':
- return MUV_UVBBState.NONE
+ return State.NONE
if event.type == 'MOUSEMOVE':
x, y = mouse_view.x, mouse_view.y
self.__cmd_exec.top().set(x, y)
- return MUV_UVBBState.ROTATING
+ return State.ROTATING
-class MUV_UVBBStateMgr():
+class StateManager():
"""
Custom class: Manage state about this feature
"""
def __init__(self, cmd_exec):
self.__cmd_exec = cmd_exec # command executer
- self.__state = MUV_UVBBState.NONE # current state
- self.__state_obj = MUV_UVBBStateNone(self.__cmd_exec)
+ self.__state = State.NONE # current state
+ self.__state_obj = StateNone(self.__cmd_exec)
def __update_state(self, next_state, ctrl_points):
"""
@@ -541,18 +568,18 @@ class MUV_UVBBStateMgr():
if next_state == self.__state:
return
obj = None
- if next_state == MUV_UVBBState.TRANSLATING:
- obj = MUV_UVBBStateTranslating(self.__cmd_exec, ctrl_points)
- elif MUV_UVBBState.SCALING_1 <= next_state <= MUV_UVBBState.SCALING_8:
- obj = MUV_UVBBStateScaling(
+ if next_state == State.TRANSLATING:
+ obj = StateTranslating(self.__cmd_exec, ctrl_points)
+ elif State.SCALING_1 <= next_state <= State.SCALING_8:
+ obj = StateScaling(
self.__cmd_exec, next_state, ctrl_points)
- elif next_state == MUV_UVBBState.ROTATING:
- obj = MUV_UVBBStateRotating(self.__cmd_exec, ctrl_points)
- elif next_state == MUV_UVBBState.NONE:
- obj = MUV_UVBBStateNone(self.__cmd_exec)
- elif (MUV_UVBBState.UNIFORM_SCALING_1 <= next_state <=
- MUV_UVBBState.UNIFORM_SCALING_4):
- obj = MUV_UVBBStateUniformScaling(
+ elif next_state == State.ROTATING:
+ obj = StateRotating(self.__cmd_exec, ctrl_points)
+ elif next_state == State.NONE:
+ obj = StateNone(self.__cmd_exec)
+ elif (State.UNIFORM_SCALING_1 <= next_state <=
+ State.UNIFORM_SCALING_4):
+ obj = StateUniformScaling(
self.__cmd_exec, next_state, ctrl_points)
if obj is not None:
@@ -569,34 +596,97 @@ class MUV_UVBBStateMgr():
context, event, ctrl_points, mouse_view)
self.__update_state(next_state, ctrl_points)
+ return self.__state
+
-class MUV_UVBBUpdater(bpy.types.Operator):
+class Operator(bpy.types.Operator):
"""
- Operation class: Update state and handle event by modal function
+ Operation class: UV Bounding Box
"""
- bl_idname = "uv.muv_uvbb_updater"
- bl_label = "UV Bounding Box Updater"
- bl_description = "Update UV Bounding Box"
+ bl_idname = "uv.muv_uv_bounding_box_operator"
+ bl_label = "UV Bounding Box"
+ bl_description = "Internal operation for UV Bounding Box"
bl_options = {'REGISTER', 'UNDO'}
def __init__(self):
self.__timer = None
- self.__cmd_exec = MUV_UVBBCmdExecuter() # Command executer
- self.__state_mgr = MUV_UVBBStateMgr(self.__cmd_exec) # State Manager
+ self.__cmd_exec = CommandExecuter() # Command executor
+ self.__state_mgr = StateManager(self.__cmd_exec) # State Manager
- def __handle_add(self, context):
- if self.__timer is None:
- self.__timer = context.window_manager.event_timer_add(
+ __handle = None
+ __timer = None
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return 1 if cls.__handle else 0
+
+ @classmethod
+ def handle_add(cls, obj, context):
+ if cls.__handle is None:
+ sie = bpy.types.SpaceImageEditor
+ cls.__handle = sie.draw_handler_add(
+ cls.draw_bb, (obj, context), "WINDOW", "POST_PIXEL")
+ if cls.__timer is None:
+ cls.__timer = context.window_manager.event_timer_add(
0.1, context.window)
- context.window_manager.modal_handler_add(self)
- MUV_UVBBRenderer.handle_add(self, context)
+ context.window_manager.modal_handler_add(obj)
- def __handle_remove(self, context):
- MUV_UVBBRenderer.handle_remove()
- if self.__timer is not None:
- context.window_manager.event_timer_remove(self.__timer)
- self.__timer = None
+ @classmethod
+ def handle_remove(cls, context):
+ if cls.__handle is not None:
+ sie = bpy.types.SpaceImageEditor
+ sie.draw_handler_remove(cls.__handle, "WINDOW")
+ cls.__handle = None
+ if cls.__timer is not None:
+ context.window_manager.event_timer_remove(cls.__timer)
+ cls.__timer = None
+
+ @classmethod
+ def __draw_ctrl_point(cls, context, pos):
+ """
+ Draw control point
+ """
+ prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+ cp_size = prefs.uv_bounding_box_cp_size
+ offset = cp_size / 2
+ verts = [
+ [pos.x - offset, pos.y - offset],
+ [pos.x - offset, pos.y + offset],
+ [pos.x + offset, pos.y + offset],
+ [pos.x + offset, pos.y - offset]
+ ]
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glBegin(bgl.GL_QUADS)
+ bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
+ for (x, y) in verts:
+ bgl.glVertex2f(x, y)
+ bgl.glEnd()
+
+ @classmethod
+ def draw_bb(cls, _, context):
+ """
+ Draw bounding box
+ """
+ props = context.scene.muv_props.uv_bounding_box
+
+ if not Operator.is_running(context):
+ return
+
+ if not is_valid_context(context):
+ return
+
+ for cp in props.ctrl_points:
+ cls.__draw_ctrl_point(
+ context, mathutils.Vector(
+ context.region.view2d.view_to_region(cp.x, cp.y)))
def __get_uv_info(self, context):
"""
@@ -615,10 +705,10 @@ class MUV_UVBBUpdater(bpy.types.Operator):
if not f.select:
continue
for i, l in enumerate(f.loops):
- if sc.muv_uvbb_boundary == 'UV_SEL':
+ if sc.muv_uv_bounding_box_boundary == 'UV_SEL':
if l[uv_layer].select:
uv_info.append((f.index, i, l[uv_layer].uv.copy()))
- elif sc.muv_uvbb_boundary == 'UV':
+ elif sc.muv_uv_bounding_box_boundary == 'UV':
uv_info.append((f.index, i, l[uv_layer].uv.copy()))
if not uv_info:
return None
@@ -688,16 +778,23 @@ class MUV_UVBBUpdater(bpy.types.Operator):
return [trans_mat * cp for cp in ctrl_points_ini]
def modal(self, context, event):
- props = context.scene.muv_props.uvbb
+ props = context.scene.muv_props.uv_bounding_box
common.redraw_all_areas()
- if props.running is False:
- self.__handle_remove(context)
+
+ if not Operator.is_running(context):
return {'FINISHED'}
- area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+ if not is_valid_context(context):
+ Operator.handle_remove(context)
+ return {'FINISHED'}
- if event.mouse_region_x < 0 or event.mouse_region_x > area.width or \
- event.mouse_region_y < 0 or event.mouse_region_y > area.height:
+ region_types = [
+ 'HEADER',
+ 'UI',
+ 'TOOLS',
+ ]
+ if not common.mouse_on_area(event, 'IMAGE_EDITOR') or \
+ common.mouse_on_regions(event, 'IMAGE_EDITOR', region_types):
return {'PASS_THROUGH'}
if event.type == 'TIMER':
@@ -706,27 +803,30 @@ class MUV_UVBBUpdater(bpy.types.Operator):
props.ctrl_points = self.__update_ctrl_point(
props.ctrl_points_ini, trans_mat)
- self.__state_mgr.update(context, props.ctrl_points, event)
+ state = self.__state_mgr.update(context, props.ctrl_points, event)
+ if state == State.NONE:
+ return {'PASS_THROUGH'}
return {'RUNNING_MODAL'}
- def execute(self, context):
- props = context.scene.muv_props.uvbb
+ def invoke(self, context, _):
+ props = context.scene.muv_props.uv_bounding_box
- if props.running is True:
- props.running = False
+ if Operator.is_running(context):
+ Operator.handle_remove(context)
return {'FINISHED'}
props.uv_info_ini = self.__get_uv_info(context)
if props.uv_info_ini is None:
return {'CANCELLED'}
+
+ Operator.handle_add(self, context)
+
props.ctrl_points_ini = self.__get_ctrl_point(props.uv_info_ini)
trans_mat = self.__cmd_exec.execute()
# Update is needed in order to display control point
self.__update_uvs(context, props.uv_info_ini, trans_mat)
props.ctrl_points = self.__update_ctrl_point(
props.ctrl_points_ini, trans_mat)
- self.__handle_add(context)
- props.running = True
return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py
index 60a754a3..0c05e03d 100644
--- a/uv_magic_uv/op/uv_inspection.py
+++ b/uv_magic_uv/op/uv_inspection.py
@@ -20,343 +20,161 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
import bpy
import bmesh
import bgl
-from mathutils import Vector
+from bpy.props import BoolProperty, EnumProperty
from .. import common
-def is_polygon_same(points1, points2):
- if len(points1) != len(points2):
- return False
-
- pts1 = points1.as_list()
- pts2 = points2.as_list()
-
- for p1 in pts1:
- for p2 in pts2:
- diff = p2 - p1
- if diff.length < 0.0000001:
- pts2.remove(p2)
- break
- else:
- return False
-
- return True
-
-
-def is_segment_intersect(start1, end1, start2, end2):
- seg1 = end1 - start1
- seg2 = end2 - start2
-
- a1 = -seg1.y
- b1 = seg1.x
- d1 = -(a1 * start1.x + b1 * start1.y)
-
- a2 = -seg2.y
- b2 = seg2.x
- d2 = -(a2 * start2.x + b2 * start2.y)
+__all__ = [
+ 'Properties',
+ 'OperatorRender',
+ 'OperatorUpdate',
+]
- seg1_line2_start = a2 * start1.x + b2 * start1.y + d2
- seg1_line2_end = a2 * end1.x + b2 * end1.y + d2
- seg2_line1_start = a1 * start2.x + b1 * start2.y + d1
- seg2_line1_end = a1 * end2.x + b1 * end2.y + d1
+def is_valid_context(context):
+ obj = context.object
- if (seg1_line2_start * seg1_line2_end >= 0) or \
- (seg2_line1_start * seg2_line1_end >= 0):
- return False, None
-
- u = seg1_line2_start / (seg1_line2_start - seg1_line2_end)
- out = start1 + u * seg1
-
- return True, out
-
-
-class RingBuffer:
- def __init__(self, arr):
- self.__buffer = arr.copy()
- self.__pointer = 0
-
- def __repr__(self):
- return repr(self.__buffer)
-
- def __len__(self):
- return len(self.__buffer)
-
- def insert(self, val, offset=0):
- self.__buffer.insert(self.__pointer + offset, val)
-
- def head(self):
- return self.__buffer[0]
-
- def tail(self):
- return self.__buffer[-1]
-
- def get(self, offset=0):
- size = len(self.__buffer)
- val = self.__buffer[(self.__pointer + offset) % size]
- return val
-
- def next(self):
- size = len(self.__buffer)
- self.__pointer = (self.__pointer + 1) % size
-
- def reset(self):
- self.__pointer = 0
-
- def find(self, obj):
- try:
- idx = self.__buffer.index(obj)
- except ValueError:
- return None
- return self.__buffer[idx]
-
- def find_and_next(self, obj):
- size = len(self.__buffer)
- idx = self.__buffer.index(obj)
- self.__pointer = (idx + 1) % size
-
- def find_and_set(self, obj):
- idx = self.__buffer.index(obj)
- self.__pointer = idx
-
- def as_list(self):
- return self.__buffer.copy()
-
- def reverse(self):
- self.__buffer.reverse()
- self.reset()
-
-
-# clip: reference polygon
-# subject: tested polygon
-def do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
-
- clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
- if is_polygon_flipped(clip_uvs):
- clip_uvs.reverse()
- subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
- if is_polygon_flipped(subject_uvs):
- subject_uvs.reverse()
-
- common.debug_print("===== Clip UV List =====")
- common.debug_print(clip_uvs)
- common.debug_print("===== Subject UV List =====")
- common.debug_print(subject_uvs)
-
- # check if clip and subject is overlapped completely
- if is_polygon_same(clip_uvs, subject_uvs):
- polygons = [subject_uvs.as_list()]
- common.debug_print("===== Polygons Overlapped Completely =====")
- common.debug_print(polygons)
- return True, polygons
-
- # check if subject is in clip
- if is_points_in_polygon(subject_uvs, clip_uvs):
- polygons = [subject_uvs.as_list()]
- return True, polygons
-
- # check if clip is in subject
- if is_points_in_polygon(clip_uvs, subject_uvs):
- polygons = [subject_uvs.as_list()]
- return True, polygons
-
- # check if clip and subject is overlapped partially
- intersections = []
- while True:
- subject_uvs.reset()
- while True:
- uv_start1 = clip_uvs.get()
- uv_end1 = clip_uvs.get(1)
- uv_start2 = subject_uvs.get()
- uv_end2 = subject_uvs.get(1)
- intersected, point = is_segment_intersect(uv_start1, uv_end1,
- uv_start2, uv_end2)
- if intersected:
- clip_uvs.insert(point, 1)
- subject_uvs.insert(point, 1)
- intersections.append([point,
- [clip_uvs.get(), clip_uvs.get(1)]])
- subject_uvs.next()
- if subject_uvs.get() == subject_uvs.head():
- break
- clip_uvs.next()
- if clip_uvs.get() == clip_uvs.head():
- break
-
- common.debug_print("===== Intersection List =====")
- common.debug_print(intersections)
-
- # no intersection, so subject and clip is not overlapped
- if not intersections:
- return False, None
-
- def get_intersection_pair(intersections, key):
- for sect in intersections:
- if sect[0] == key:
- return sect[1]
-
- return None
-
- # make enter/exit pair
- subject_uvs.reset()
- subject_entering = []
- subject_exiting = []
- clip_entering = []
- clip_exiting = []
- intersect_uv_list = []
- while True:
- pair = get_intersection_pair(intersections, subject_uvs.get())
- if pair:
- sub = subject_uvs.get(1) - subject_uvs.get(-1)
- inter = pair[1] - pair[0]
- cross = sub.x * inter.y - inter.x * sub.y
- if cross < 0:
- subject_entering.append(subject_uvs.get())
- clip_exiting.append(subject_uvs.get())
- else:
- subject_exiting.append(subject_uvs.get())
- clip_entering.append(subject_uvs.get())
- intersect_uv_list.append(subject_uvs.get())
-
- subject_uvs.next()
- if subject_uvs.get() == subject_uvs.head():
- break
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
- common.debug_print("===== Enter List =====")
- common.debug_print(clip_entering)
- common.debug_print(subject_entering)
- common.debug_print("===== Exit List =====")
- common.debug_print(clip_exiting)
- common.debug_print(subject_exiting)
-
- # for now, can't handle the situation when fulfill all below conditions
- # * two faces have common edge
- # * each face is intersected
- # * Show Mode is "Part"
- # so for now, ignore this situation
- if len(subject_entering) != len(subject_exiting):
- if mode == 'FACE':
- polygons = [subject_uvs.as_list()]
- return True, polygons
- return False, None
-
- def traverse(current_list, entering, exiting, poly, current, other_list):
- result = current_list.find(current)
- if not result:
- return None
- if result != current:
- print("Internal Error")
- return None
-
- # enter
- if entering.count(current) >= 1:
- entering.remove(current)
-
- current_list.find_and_next(current)
- current = current_list.get()
-
- while exiting.count(current) == 0:
- poly.append(current.copy())
- current_list.find_and_next(current)
- current = current_list.get()
-
- # exit
- poly.append(current.copy())
- exiting.remove(current)
-
- other_list.find_and_set(current)
- return other_list.get()
-
- # Traverse
- polygons = []
- current_uv_list = subject_uvs
- other_uv_list = clip_uvs
- current_entering = subject_entering
- current_exiting = subject_exiting
-
- poly = []
- current_uv = current_entering[0]
-
- while True:
- current_uv = traverse(current_uv_list, current_entering,
- current_exiting, poly, current_uv, other_uv_list)
-
- if current_uv_list == subject_uvs:
- current_uv_list = clip_uvs
- other_uv_list = subject_uvs
- current_entering = clip_entering
- current_exiting = clip_exiting
- common.debug_print("-- Next: Clip --")
- else:
- current_uv_list = subject_uvs
- other_uv_list = clip_uvs
- current_entering = subject_entering
- current_exiting = subject_exiting
- common.debug_print("-- Next: Subject --")
-
- common.debug_print(clip_entering)
- common.debug_print(clip_exiting)
- common.debug_print(subject_entering)
- common.debug_print(subject_exiting)
-
- if not clip_entering and not clip_exiting \
- and not subject_entering and not subject_exiting:
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ for space in context.area.spaces:
+ if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
break
+ else:
+ return False
- polygons.append(poly)
-
- common.debug_print("===== Polygons Overlapped Partially =====")
- common.debug_print(polygons)
-
- return True, polygons
+ return True
-class MUV_UVInspRenderer(bpy.types.Operator):
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ class Props():
+ overlapped_info = []
+ flipped_info = []
+
+ scene.muv_props.uv_inspection = Props()
+
+ def get_func(_):
+ return OperatorRender.is_running(bpy.context)
+
+ def set_func(_, __):
+ pass
+
+ def update_func(_, __):
+ bpy.ops.uv.muv_uv_inspection_operator_render('INVOKE_REGION_WIN')
+
+ scene.muv_uv_inspection_enabled = BoolProperty(
+ name="UV Inspection Enabled",
+ description="UV Inspection is enabled",
+ default=False
+ )
+ scene.muv_uv_inspection_show = BoolProperty(
+ name="UV Inspection Showed",
+ description="UV Inspection is showed",
+ default=False,
+ get=get_func,
+ set=set_func,
+ update=update_func
+ )
+ scene.muv_uv_inspection_show_overlapped = BoolProperty(
+ name="Overlapped",
+ description="Show overlapped UVs",
+ default=False
+ )
+ scene.muv_uv_inspection_show_flipped = BoolProperty(
+ name="Flipped",
+ description="Show flipped UVs",
+ default=False
+ )
+ scene.muv_uv_inspection_show_mode = EnumProperty(
+ name="Mode",
+ description="Show mode",
+ items=[
+ ('PART', "Part", "Show only overlapped/flipped part"),
+ ('FACE', "Face", "Show overlapped/flipped face")
+ ],
+ default='PART'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_props.uv_inspection
+ del scene.muv_uv_inspection_enabled
+ del scene.muv_uv_inspection_show
+ del scene.muv_uv_inspection_show_overlapped
+ del scene.muv_uv_inspection_show_flipped
+ del scene.muv_uv_inspection_show_mode
+
+
+class OperatorRender(bpy.types.Operator):
"""
Operation class: Render UV Inspection
No operation (only rendering)
"""
- bl_idname = "uv.muv_uvinsp_renderer"
+ bl_idname = "uv.muv_uv_inspection_operator_render"
bl_description = "Render overlapped/flipped UVs"
bl_label = "Overlapped/Flipped UV renderer"
__handle = None
- @staticmethod
- def handle_add(obj, context):
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return 1 if cls.__handle else 0
+
+ @classmethod
+ def handle_add(cls, obj, context):
sie = bpy.types.SpaceImageEditor
- MUV_UVInspRenderer.__handle = sie.draw_handler_add(
- MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL')
+ cls.__handle = sie.draw_handler_add(
+ OperatorRender.draw, (obj, context), 'WINDOW', 'POST_PIXEL')
- @staticmethod
- def handle_remove():
- if MUV_UVInspRenderer.__handle is not None:
+ @classmethod
+ def handle_remove(cls):
+ if cls.__handle is not None:
bpy.types.SpaceImageEditor.draw_handler_remove(
- MUV_UVInspRenderer.__handle, 'WINDOW')
- MUV_UVInspRenderer.__handle = None
+ cls.__handle, 'WINDOW')
+ cls.__handle = None
@staticmethod
def draw(_, context):
sc = context.scene
- props = sc.muv_props.uvinsp
+ props = sc.muv_props.uv_inspection
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
+ if not OperatorRender.is_running(context):
+ return
+
# OpenGL configuration
bgl.glEnable(bgl.GL_BLEND)
# render overlapped UV
- if sc.muv_uvinsp_show_overlapped:
- color = prefs.uvinsp_overlapped_color
+ if sc.muv_uv_inspection_show_overlapped:
+ color = prefs.uv_inspection_overlapped_color
for info in props.overlapped_info:
- if sc.muv_uvinsp_show_mode == 'PART':
+ if sc.muv_uv_inspection_show_mode == 'PART':
for poly in info["polygons"]:
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
@@ -365,7 +183,7 @@ class MUV_UVInspRenderer(bpy.types.Operator):
uv.x, uv.y)
bgl.glVertex2f(x, y)
bgl.glEnd()
- elif sc.muv_uvinsp_show_mode == 'FACE':
+ elif sc.muv_uv_inspection_show_mode == 'FACE':
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in info["subject_uvs"]:
@@ -374,10 +192,10 @@ class MUV_UVInspRenderer(bpy.types.Operator):
bgl.glEnd()
# render flipped UV
- if sc.muv_uvinsp_show_flipped:
- color = prefs.uvinsp_flipped_color
+ if sc.muv_uv_inspection_show_flipped:
+ color = prefs.uv_inspection_flipped_color
for info in props.flipped_info:
- if sc.muv_uvinsp_show_mode == 'PART':
+ if sc.muv_uv_inspection_show_mode == 'PART':
for poly in info["polygons"]:
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
@@ -386,7 +204,7 @@ class MUV_UVInspRenderer(bpy.types.Operator):
uv.x, uv.y)
bgl.glVertex2f(x, y)
bgl.glEnd()
- elif sc.muv_uvinsp_show_mode == 'FACE':
+ elif sc.muv_uv_inspection_show_mode == 'FACE':
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in info["uvs"]:
@@ -394,100 +212,22 @@ class MUV_UVInspRenderer(bpy.types.Operator):
bgl.glVertex2f(x, y)
bgl.glEnd()
+ def invoke(self, context, _):
+ if not OperatorRender.is_running(context):
+ update_uvinsp_info(context)
+ OperatorRender.handle_add(self, context)
+ else:
+ OperatorRender.handle_remove()
-def is_polygon_flipped(points):
- area = 0.0
- for i in range(len(points)):
- uv1 = points.get(i)
- uv2 = points.get(i + 1)
- a = uv1.x * uv2.y - uv1.y * uv2.x
- area = area + a
- if area < 0:
- # clock-wise
- return True
- return False
-
-
-def is_point_in_polygon(point, subject_points):
- count = 0
- for i in range(len(subject_points)):
- uv_start1 = subject_points.get(i)
- uv_end1 = subject_points.get(i + 1)
- uv_start2 = point
- uv_end2 = Vector((1000000.0, point.y))
- intersected, _ = is_segment_intersect(uv_start1, uv_end1,
- uv_start2, uv_end2)
- if intersected:
- count = count + 1
-
- return count % 2
-
-
-def is_points_in_polygon(points, subject_points):
- for i in range(len(points)):
- internal = is_point_in_polygon(points.get(i), subject_points)
- if not internal:
- return False
-
- return True
-
+ if context.area:
+ context.area.tag_redraw()
-def get_overlapped_uv_info(bm, faces, uv_layer, mode):
- # at first, check island overlapped
- isl = common.get_island_info_from_faces(bm, faces, uv_layer)
- overlapped_isl_pairs = []
- for i, i1 in enumerate(isl):
- for i2 in isl[i + 1:]:
- if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \
- (i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y):
- continue
- overlapped_isl_pairs.append([i1, i2])
-
- # next, check polygon overlapped
- overlapped_uvs = []
- for oip in overlapped_isl_pairs:
- for clip in oip[0]["faces"]:
- f_clip = clip["face"]
- for subject in oip[1]["faces"]:
- f_subject = subject["face"]
-
- # fast operation, apply bounding box algorithm
- if (clip["max_uv"].x < subject["min_uv"].x) or \
- (subject["max_uv"].x < clip["min_uv"].x) or \
- (clip["max_uv"].y < subject["min_uv"].y) or \
- (subject["max_uv"].y < clip["min_uv"].y):
- continue
-
- # slow operation, apply Weiler-Atherton cliping algorithm
- result, polygons = do_weiler_atherton_cliping(f_clip,
- f_subject,
- uv_layer, mode)
- if result:
- subject_uvs = [l[uv_layer].uv.copy()
- for l in f_subject.loops]
- overlapped_uvs.append({"clip_face": f_clip,
- "subject_face": f_subject,
- "subject_uvs": subject_uvs,
- "polygons": polygons})
-
- return overlapped_uvs
-
-
-def get_flipped_uv_info(faces, uv_layer):
- flipped_uvs = []
- for f in faces:
- polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
- if is_polygon_flipped(polygon):
- uvs = [l[uv_layer].uv.copy() for l in f.loops]
- flipped_uvs.append({"face": f, "uvs": uvs,
- "polygons": [polygon.as_list()]})
-
- return flipped_uvs
+ return {'FINISHED'}
def update_uvinsp_info(context):
sc = context.scene
- props = sc.muv_props.uvinsp
+ props = sc.muv_props.uv_inspection
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
@@ -499,125 +239,34 @@ def update_uvinsp_info(context):
sel_faces = [f for f in bm.faces]
else:
sel_faces = [f for f in bm.faces if f.select]
- props.overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
- sc.muv_uvinsp_show_mode)
- props.flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
+ props.overlapped_info = common.get_overlapped_uv_info(
+ bm, sel_faces, uv_layer, sc.muv_uv_inspection_show_mode)
+ props.flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
-class MUV_UVInspUpdate(bpy.types.Operator):
+class OperatorUpdate(bpy.types.Operator):
"""
Operation class: Update
"""
- bl_idname = "uv.muv_uvinsp_update"
- bl_label = "Update"
- bl_description = "Update Overlapped/Flipped UV"
+ bl_idname = "uv.muv_uv_inspection_operator_update"
+ bl_label = "Update UV Inspection"
+ bl_description = "Update UV Inspection"
bl_options = {'REGISTER', 'UNDO'}
- def execute(self, context):
- update_uvinsp_info(context)
-
- if context.area:
- context.area.tag_redraw()
-
- return {'FINISHED'}
-
-
-class MUV_UVInspDisplay(bpy.types.Operator):
- """
- Operation class: Display
- """
-
- bl_idname = "uv.muv_uvinsp_display"
- bl_label = "Display"
- bl_description = "Display Overlapped/Flipped UV"
- bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ if not OperatorRender.is_running(context):
+ return False
+ return is_valid_context(context)
def execute(self, context):
- sc = context.scene
- props = sc.muv_props.uvinsp
- if not props.display_running:
- update_uvinsp_info(context)
- MUV_UVInspRenderer.handle_add(self, context)
- props.display_running = True
- else:
- MUV_UVInspRenderer.handle_remove()
- props.display_running = False
+ update_uvinsp_info(context)
if context.area:
context.area.tag_redraw()
return {'FINISHED'}
-
-
-class MUV_UVInspSelectOverlapped(bpy.types.Operator):
- """
- Operation class: Select faces which have overlapped UVs
- """
-
- bl_idname = "uv.muv_uvinsp_select_overlapped"
- bl_label = "Overlapped"
- bl_description = "Select faces which have overlapped UVs"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- if context.tool_settings.use_uv_select_sync:
- sel_faces = [f for f in bm.faces]
- else:
- sel_faces = [f for f in bm.faces if f.select]
-
- overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer,
- 'FACE')
-
- for info in overlapped_info:
- if context.tool_settings.use_uv_select_sync:
- info["subject_face"].select = True
- else:
- for l in info["subject_face"].loops:
- l[uv_layer].select = True
-
- bmesh.update_edit_mesh(obj.data)
-
- return {'FINISHED'}
-
-
-class MUV_UVInspSelectFlipped(bpy.types.Operator):
- """
- Operation class: Select faces which have flipped UVs
- """
-
- bl_idname = "uv.muv_uvinsp_select_flipped"
- bl_label = "Flipped"
- bl_description = "Select faces which have flipped UVs"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- if context.tool_settings.use_uv_select_sync:
- sel_faces = [f for f in bm.faces]
- else:
- sel_faces = [f for f in bm.faces if f.select]
-
- flipped_info = get_flipped_uv_info(sel_faces, uv_layer)
-
- for info in flipped_info:
- if context.tool_settings.use_uv_select_sync:
- info["face"].select = True
- else:
- for l in info["face"].loops:
- l[uv_layer].select = True
-
- bmesh.update_edit_mesh(obj.data)
-
- return {'FINISHED'}
diff --git a/uv_magic_uv/op/uv_sculpt.py b/uv_magic_uv/op/uv_sculpt.py
index 2bf76abd..63c1adfe 100644
--- a/uv_magic_uv/op/uv_sculpt.py
+++ b/uv_magic_uv/op/uv_sculpt.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from math import pi, cos, tan, sin
@@ -32,39 +32,172 @@ from mathutils import Vector
from bpy_extras import view3d_utils
from mathutils.bvhtree import BVHTree
from mathutils.geometry import barycentric_transform
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ EnumProperty,
+ FloatProperty,
+)
from .. import common
-class MUV_UVSculptRenderer(bpy.types.Operator):
+__all__ = [
+ 'Properties',
+ 'Operator',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ def get_func(_):
+ return Operator.is_running(bpy.context)
+
+ def set_func(_, __):
+ pass
+
+ def update_func(_, __):
+ bpy.ops.uv.muv_uv_sculpt_operator('INVOKE_REGION_WIN')
+
+ scene.muv_uv_sculpt_enabled = BoolProperty(
+ name="UV Sculpt",
+ description="UV Sculpt is enabled",
+ default=False
+ )
+ scene.muv_uv_sculpt_enable = BoolProperty(
+ name="UV Sculpt Showed",
+ description="UV Sculpt is enabled",
+ default=False,
+ get=get_func,
+ set=set_func,
+ update=update_func
+ )
+ scene.muv_uv_sculpt_radius = IntProperty(
+ name="Radius",
+ description="Radius of the brush",
+ min=1,
+ max=500,
+ default=30
+ )
+ scene.muv_uv_sculpt_strength = FloatProperty(
+ name="Strength",
+ description="How powerful the effect of the brush when applied",
+ min=0.0,
+ max=1.0,
+ default=0.03,
+ )
+ scene.muv_uv_sculpt_tools = EnumProperty(
+ name="Tools",
+ description="Select Tools for the UV sculpt brushes",
+ items=[
+ ('GRAB', "Grab", "Grab UVs"),
+ ('RELAX', "Relax", "Relax UVs"),
+ ('PINCH', "Pinch", "Pinch UVs")
+ ],
+ default='GRAB'
+ )
+ scene.muv_uv_sculpt_show_brush = BoolProperty(
+ name="Show Brush",
+ description="Show Brush",
+ default=True
+ )
+ scene.muv_uv_sculpt_pinch_invert = BoolProperty(
+ name="Invert",
+ description="Pinch UV to invert direction",
+ default=False
+ )
+ scene.muv_uv_sculpt_relax_method = EnumProperty(
+ name="Method",
+ description="Algorithm used for relaxation",
+ items=[
+ ('HC', "HC", "Use HC method for relaxation"),
+ ('LAPLACIAN', "Laplacian",
+ "Use laplacian method for relaxation")
+ ],
+ default='HC'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_uv_sculpt_enabled
+ del scene.muv_uv_sculpt_enable
+ del scene.muv_uv_sculpt_radius
+ del scene.muv_uv_sculpt_strength
+ del scene.muv_uv_sculpt_tools
+ del scene.muv_uv_sculpt_show_brush
+ del scene.muv_uv_sculpt_pinch_invert
+ del scene.muv_uv_sculpt_relax_method
+
+
+class Operator(bpy.types.Operator):
"""
- Operation class: Render Brush
+ Operation class: UV Sculpt in View3D
"""
- bl_idname = "uv.muv_uvsculpt_renderer"
- bl_label = "Brush Renderer"
- bl_description = "Brush Renderer in View3D"
+ bl_idname = "uv.muv_uv_sculpt_operator"
+ bl_label = "UV Sculpt"
+ bl_description = "UV Sculpt in View3D"
+ bl_options = {'REGISTER'}
__handle = None
-
- @staticmethod
- def handle_add(obj, context):
- if MUV_UVSculptRenderer.__handle is None:
+ __timer = None
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return False
+ return is_valid_context(context)
+
+ @classmethod
+ def is_running(cls, _):
+ return 1 if cls.__handle else 0
+
+ @classmethod
+ def handle_add(cls, obj, context):
+ if not cls.__handle:
sv = bpy.types.SpaceView3D
- MUV_UVSculptRenderer.__handle = sv.draw_handler_add(
- MUV_UVSculptRenderer.draw_brush,
- (obj, context), "WINDOW", "POST_PIXEL")
+ cls.__handle = sv.draw_handler_add(cls.draw_brush, (obj, context),
+ "WINDOW", "POST_PIXEL")
+ if not cls.__timer:
+ cls.__timer = context.window_manager.event_timer_add(
+ 0.1, context.window)
+ context.window_manager.modal_handler_add(obj)
- @staticmethod
- def handle_remove():
- if MUV_UVSculptRenderer.__handle is not None:
+ @classmethod
+ def handle_remove(cls, context):
+ if cls.__handle:
sv = bpy.types.SpaceView3D
- sv.draw_handler_remove(
- MUV_UVSculptRenderer.__handle, "WINDOW")
- MUV_UVSculptRenderer.__handle = None
-
- @staticmethod
- def draw_brush(obj, context):
+ sv.draw_handler_remove(cls.__handle, "WINDOW")
+ cls.__handle = None
+ if cls.__timer:
+ context.window_manager.event_timer_remove(cls.__timer)
+ cls.__timer = None
+
+ @classmethod
+ def draw_brush(cls, obj, context):
sc = context.scene
prefs = context.user_preferences.addons["uv_magic_uv"].preferences
@@ -72,12 +205,12 @@ class MUV_UVSculptRenderer(bpy.types.Operator):
theta = 2 * pi / num_segment
fact_t = tan(theta)
fact_r = cos(theta)
- color = prefs.uvsculpt_brush_color
+ color = prefs.uv_sculpt_brush_color
bgl.glBegin(bgl.GL_LINE_STRIP)
bgl.glColor4f(color[0], color[1], color[2], color[3])
- x = sc.muv_uvsculpt_radius * cos(0.0)
- y = sc.muv_uvsculpt_radius * sin(0.0)
+ x = sc.muv_uv_sculpt_radius * cos(0.0)
+ y = sc.muv_uv_sculpt_radius * sin(0.0)
for _ in range(num_segment):
bgl.glVertex2f(x + obj.current_mco.x, y + obj.current_mco.y)
tx = -y
@@ -88,19 +221,7 @@ class MUV_UVSculptRenderer(bpy.types.Operator):
y = y * fact_r
bgl.glEnd()
-
-class MUV_UVSculptOps(bpy.types.Operator):
- """
- Operation class: UV Sculpt in View3D
- """
-
- bl_idname = "uv.muv_uvsculpt_ops"
- bl_label = "UV Sculpt"
- bl_description = "UV Sculpt in View3D"
- bl_options = {'REGISTER'}
-
def __init__(self):
- self.__timer = None
self.__loop_info = []
self.__stroking = False
self.current_mco = Vector((0.0, 0.0))
@@ -137,7 +258,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
loc_2d = view3d_utils.location_3d_to_region_2d(
region, space.region_3d, world_mat * l.vert.co)
diff = loc_2d - self.__initial_mco
- if diff.length < sc.muv_uvsculpt_radius:
+ if diff.length < sc.muv_uv_sculpt_radius:
info = {
"face_idx": f.index,
"loop_idx": i,
@@ -145,8 +266,8 @@ class MUV_UVSculptOps(bpy.types.Operator):
"initial_vco_2d": loc_2d,
"initial_uv": l[uv_layer].uv.copy(),
"strength": self.__get_strength(
- diff.length, sc.muv_uvsculpt_radius,
- sc.muv_uvsculpt_strength)
+ diff.length, sc.muv_uv_sculpt_radius,
+ sc.muv_uv_sculpt_strength)
}
self.__loop_info.append(info)
@@ -158,13 +279,13 @@ class MUV_UVSculptOps(bpy.types.Operator):
uv_layer = bm.loops.layers.uv.verify()
mco = self.current_mco
- if sc.muv_uvsculpt_tools == 'GRAB':
+ if sc.muv_uv_sculpt_tools == 'GRAB':
for info in self.__loop_info:
diff_uv = (mco - self.__initial_mco) * info["strength"]
l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
- elif sc.muv_uvsculpt_tools == 'PINCH':
+ elif sc.muv_uv_sculpt_tools == 'PINCH':
_, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
loop_info = []
for f in bm.faces:
@@ -174,7 +295,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
loc_2d = view3d_utils.location_3d_to_region_2d(
region, space.region_3d, world_mat * l.vert.co)
diff = loc_2d - self.__initial_mco
- if diff.length < sc.muv_uvsculpt_radius:
+ if diff.length < sc.muv_uv_sculpt_radius:
info = {
"face_idx": f.index,
"loop_idx": i,
@@ -182,8 +303,8 @@ class MUV_UVSculptOps(bpy.types.Operator):
"initial_vco_2d": loc_2d,
"initial_uv": l[uv_layer].uv.copy(),
"strength": self.__get_strength(
- diff.length, sc.muv_uvsculpt_radius,
- sc.muv_uvsculpt_strength)
+ diff.length, sc.muv_uv_sculpt_radius,
+ sc.muv_uv_sculpt_strength)
}
loop_info.append(info)
@@ -215,13 +336,13 @@ class MUV_UVSculptOps(bpy.types.Operator):
# move to target UV coordinate
for info in loop_info:
l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
- if sc.muv_uvsculpt_pinch_invert:
+ if sc.muv_uv_sculpt_pinch_invert:
diff_uv = (l[uv_layer].uv - target_uv) * info["strength"]
else:
diff_uv = (target_uv - l[uv_layer].uv) * info["strength"]
l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0
- elif sc.muv_uvsculpt_tools == 'RELAX':
+ elif sc.muv_uv_sculpt_tools == 'RELAX':
_, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
# get vertex and loop relation
@@ -265,19 +386,19 @@ class MUV_UVSculptOps(bpy.types.Operator):
loc_2d = view3d_utils.location_3d_to_region_2d(
region, space.region_3d, world_mat * l.vert.co)
diff = loc_2d - self.__initial_mco
- if diff.length >= sc.muv_uvsculpt_radius:
+ if diff.length >= sc.muv_uv_sculpt_radius:
continue
db = vert_db[l.vert]
strength = self.__get_strength(diff.length,
- sc.muv_uvsculpt_radius,
- sc.muv_uvsculpt_strength)
+ sc.muv_uv_sculpt_radius,
+ sc.muv_uv_sculpt_strength)
base = (1.0 - strength) * l[uv_layer].uv
- if sc.muv_uvsculpt_relax_method == 'HC':
+ if sc.muv_uv_sculpt_relax_method == 'HC':
t = 0.5 * (db["uv_b"] + db["uv_sum_b"] / d["uv_count"])
diff = strength * (db["uv_p"] - t)
target_uv = base + diff
- elif sc.muv_uvsculpt_relax_method == 'LAPLACIAN':
+ elif sc.muv_uv_sculpt_relax_method == 'LAPLACIAN':
diff = strength * db["uv_p"]
target_uv = base + diff
else:
@@ -294,7 +415,7 @@ class MUV_UVSculptOps(bpy.types.Operator):
uv_layer = bm.loops.layers.uv.verify()
mco = self.current_mco
- if sc.muv_uvsculpt_tools == 'GRAB':
+ if sc.muv_uv_sculpt_tools == 'GRAB':
for info in self.__loop_info:
diff_uv = (mco - self.__initial_mco) * info["strength"]
l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
@@ -303,23 +424,24 @@ class MUV_UVSculptOps(bpy.types.Operator):
bmesh.update_edit_mesh(obj.data)
def modal(self, context, event):
- props = context.scene.muv_props.uvsculpt
-
if context.area:
context.area.tag_redraw()
- if not props.running:
- if self.__timer is not None:
- MUV_UVSculptRenderer.handle_remove()
- context.window_manager.event_timer_remove(self.__timer)
- self.__timer = None
+ if not Operator.is_running(context):
+ Operator.handle_remove(context)
+
return {'FINISHED'}
self.current_mco = Vector((event.mouse_region_x, event.mouse_region_y))
- area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
- if self.current_mco.x < 0 or self.current_mco.x > area.width or \
- self.current_mco.y < 0 or self.current_mco.y > area.height:
+ region_types = [
+ 'HEADER',
+ 'UI',
+ 'TOOLS',
+ 'TOOL_PROPS',
+ ]
+ if not common.mouse_on_area(event, 'VIEW_3D') or \
+ common.mouse_on_regions(event, 'VIEW_3D', region_types):
return {'PASS_THROUGH'}
if event.type == 'LEFTMOUSE':
@@ -331,30 +453,25 @@ class MUV_UVSculptOps(bpy.types.Operator):
if self.__stroking:
self.__stroke_exit(context, event)
self.__stroking = False
+ return {'RUNNING_MODAL'}
elif event.type == 'MOUSEMOVE':
if self.__stroking:
self.__stroke_apply(context, event)
+ return {'RUNNING_MODAL'}
elif event.type == 'TIMER':
if self.__stroking:
self.__stroke_apply(context, event)
+ return {'RUNNING_MODAL'}
- return {'RUNNING_MODAL'}
+ return {'PASS_THROUGH'}
def invoke(self, context, _):
- props = context.scene.muv_props.uvsculpt
-
if context.area:
context.area.tag_redraw()
- if props.running:
- props.running = False
- return {'FINISHED'}
-
- props.running = True
- if self.__timer is None:
- self.__timer = context.window_manager.event_timer_add(
- 0.1, context.window)
- context.window_manager.modal_handler_add(self)
- MUV_UVSculptRenderer.handle_add(self, context)
+ if Operator.is_running(context):
+ Operator.handle_remove(context)
+ else:
+ Operator.handle_add(self, context)
return {'RUNNING_MODAL'}
diff --git a/uv_magic_uv/op/uvw.py b/uv_magic_uv/op/uvw.py
index 10202677..44858187 100644
--- a/uv_magic_uv/op/uvw.py
+++ b/uv_magic_uv/op/uvw.py
@@ -20,8 +20,8 @@
__author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from math import sin, cos, pi
@@ -37,8 +37,56 @@ from mathutils import Vector
from .. import common
-class MUV_UVWBoxMap(bpy.types.Operator):
- bl_idname = "uv.muv_uvw_box_map"
+__all__ = [
+ 'Properties',
+ 'OperatorBoxMap',
+ 'OperatorBestPlanerMap',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_uvw_enabled = BoolProperty(
+ name="UVW Enabled",
+ description="UVW is enabled",
+ default=False
+ )
+ scene.muv_uvw_assign_uvmap = BoolProperty(
+ name="Assign UVMap",
+ description="Assign UVMap when no UVmaps are available",
+ default=True
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_uvw_enabled
+ del scene.muv_uvw_assign_uvmap
+
+
+class OperatorBoxMap(bpy.types.Operator):
+ bl_idname = "uv.muv_uvw_operator_box_map"
bl_label = "Box Map"
bl_options = {'REGISTER', 'UNDO'}
@@ -70,8 +118,10 @@ class MUV_UVWBoxMap(bpy.types.Operator):
@classmethod
def poll(cls, context):
- obj = context.active_object
- return obj and obj.type == 'MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
obj = context.active_object
@@ -151,8 +201,8 @@ class MUV_UVWBoxMap(bpy.types.Operator):
return {'FINISHED'}
-class MUV_UVWBestPlanerMap(bpy.types.Operator):
- bl_idname = "uv.muv_uvw_best_planer_map"
+class OperatorBestPlanerMap(bpy.types.Operator):
+ bl_idname = "uv.muv_uvw_operator_best_planer_map"
bl_label = "Best Planer Map"
bl_options = {'REGISTER', 'UNDO'}
@@ -183,8 +233,10 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator):
@classmethod
def poll(cls, context):
- obj = context.active_object
- return obj and obj.type == 'MESH'
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
def execute(self, context):
obj = context.active_object
diff --git a/uv_magic_uv/op/world_scale_uv.py b/uv_magic_uv/op/world_scale_uv.py
index e256fbac..e1a44954 100644
--- a/uv_magic_uv/op/world_scale_uv.py
+++ b/uv_magic_uv/op/world_scale_uv.py
@@ -20,25 +20,60 @@
__author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "5.1"
-__date__ = "24 Feb 2018"
+__version__ = "5.2"
+__date__ = "17 Nov 2018"
from math import sqrt
import bpy
import bmesh
from mathutils import Vector
-from bpy.props import EnumProperty
+from bpy.props import (
+ EnumProperty,
+ FloatProperty,
+ IntVectorProperty,
+ BoolProperty,
+)
from .. import common
-def measure_wsuv_info(obj):
+__all__ = [
+ 'Properties',
+ 'OperatorMeasure',
+ 'OperatorApplyManual',
+ 'OperatorApplyScalingDensity',
+ 'OperatorApplyProportionalToMesh',
+]
+
+
+def is_valid_context(context):
+ obj = context.object
+
+ # only edit mode is allowed to execute
+ if obj is None:
+ return False
+ if obj.type != 'MESH':
+ return False
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ for space in context.area.spaces:
+ if space.type == 'VIEW_3D':
+ break
+ else:
+ return False
+
+ return True
+
+
+def measure_wsuv_info(obj, tex_size=None):
mesh_area = common.measure_mesh_area(obj)
- uv_area = common.measure_uv_area(obj)
+ uv_area = common.measure_uv_area(obj, tex_size)
if not uv_area:
- return None, None, None
+ return None, mesh_area, None
if mesh_area == 0.0:
density = 0.0
@@ -48,16 +83,112 @@ def measure_wsuv_info(obj):
return uv_area, mesh_area, density
-class MUV_WSUVMeasure(bpy.types.Operator):
+class Properties:
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_world_scale_uv_enabled = BoolProperty(
+ name="World Scale UV Enabled",
+ description="World Scale UV is enabled",
+ default=False
+ )
+ scene.muv_world_scale_uv_src_mesh_area = FloatProperty(
+ name="Mesh Area",
+ description="Source Mesh Area",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_world_scale_uv_src_uv_area = FloatProperty(
+ name="UV Area",
+ description="Source UV Area",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_world_scale_uv_src_density = FloatProperty(
+ name="Density",
+ description="Source Texel Density",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_world_scale_uv_tgt_density = FloatProperty(
+ name="Density",
+ description="Target Texel Density",
+ default=0.0,
+ min=0.0
+ )
+ scene.muv_world_scale_uv_tgt_scaling_factor = FloatProperty(
+ name="Scaling Factor",
+ default=1.0,
+ max=1000.0,
+ min=0.00001
+ )
+ scene.muv_world_scale_uv_tgt_texture_size = IntVectorProperty(
+ name="Texture Size",
+ size=2,
+ min=1,
+ soft_max=10240,
+ default=(1024, 1024),
+ )
+ scene.muv_world_scale_uv_mode = EnumProperty(
+ name="Mode",
+ description="Density calculation mode",
+ items=[
+ ('PROPORTIONAL_TO_MESH', "Proportional to Mesh",
+ "Apply density proportionaled by mesh size"),
+ ('SCALING_DENSITY', "Scaling Density",
+ "Apply scaled density from source"),
+ ('SAME_DENSITY', "Same Density",
+ "Apply same density of source"),
+ ('MANUAL', "Manual", "Specify density and size by manual"),
+ ],
+ default='MANUAL'
+ )
+ scene.muv_world_scale_uv_origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', "Center", "Center"),
+ ('LEFT_TOP', "Left Top", "Left Bottom"),
+ ('LEFT_CENTER', "Left Center", "Left Center"),
+ ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+ ('CENTER_TOP', "Center Top", "Center Top"),
+ ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+ ('RIGHT_TOP', "Right Top", "Right Top"),
+ ('RIGHT_CENTER', "Right Center", "Right Center"),
+ ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+ ],
+ default='CENTER'
+ )
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_world_scale_uv_enabled
+ del scene.muv_world_scale_uv_src_mesh_area
+ del scene.muv_world_scale_uv_src_uv_area
+ del scene.muv_world_scale_uv_src_density
+ del scene.muv_world_scale_uv_tgt_density
+ del scene.muv_world_scale_uv_tgt_scaling_factor
+ del scene.muv_world_scale_uv_mode
+ del scene.muv_world_scale_uv_origin
+
+
+class OperatorMeasure(bpy.types.Operator):
"""
Operation class: Measure face size
"""
- bl_idname = "uv.muv_wsuv_measure"
- bl_label = "Measure"
+ bl_idname = "uv.muv_world_scale_uv_operator_measure"
+ bl_label = "Measure World Scale UV"
bl_description = "Measure face size for scale calculation"
bl_options = {'REGISTER', 'UNDO'}
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
def execute(self, context):
sc = context.scene
obj = context.active_object
@@ -68,9 +199,9 @@ class MUV_WSUVMeasure(bpy.types.Operator):
"Object must have more than one UV map and texture")
return {'CANCELLED'}
- sc.muv_wsuv_src_uv_area = uv_area
- sc.muv_wsuv_src_mesh_area = mesh_area
- sc.muv_wsuv_src_density = density
+ sc.muv_world_scale_uv_src_uv_area = uv_area
+ sc.muv_world_scale_uv_src_mesh_area = mesh_area
+ sc.muv_world_scale_uv_src_density = density
self.report({'INFO'},
"UV Area: {0}, Mesh Area: {1}, Texel Density: {2}"
@@ -79,41 +210,264 @@ class MUV_WSUVMeasure(bpy.types.Operator):
return {'FINISHED'}
-class MUV_WSUVApply(bpy.types.Operator):
+def apply(obj, origin, factor):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # calculate origin
+ if origin == 'CENTER':
+ origin = Vector((0.0, 0.0))
+ num = 0
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin = origin + uv
+ num = num + 1
+ origin = origin / num
+ elif origin == 'LEFT_TOP':
+ origin = Vector((100000.0, -100000.0))
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = max(origin.y, uv.y)
+ elif origin == 'LEFT_CENTER':
+ origin = Vector((100000.0, 0.0))
+ num = 0
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = origin.y + uv.y
+ num = num + 1
+ origin.y = origin.y / num
+ elif origin == 'LEFT_BOTTOM':
+ origin = Vector((100000.0, 100000.0))
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = min(origin.y, uv.y)
+ elif origin == 'CENTER_TOP':
+ origin = Vector((0.0, -100000.0))
+ num = 0
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = origin.x + uv.x
+ origin.y = max(origin.y, uv.y)
+ num = num + 1
+ origin.x = origin.x / num
+ elif origin == 'CENTER_BOTTOM':
+ origin = Vector((0.0, 100000.0))
+ num = 0
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = origin.x + uv.x
+ origin.y = min(origin.y, uv.y)
+ num = num + 1
+ origin.x = origin.x / num
+ elif origin == 'RIGHT_TOP':
+ origin = Vector((-100000.0, -100000.0))
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = max(origin.y, uv.y)
+ elif origin == 'RIGHT_CENTER':
+ origin = Vector((-100000.0, 0.0))
+ num = 0
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = origin.y + uv.y
+ num = num + 1
+ origin.y = origin.y / num
+ elif origin == 'RIGHT_BOTTOM':
+ origin = Vector((-100000.0, 100000.0))
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = min(origin.y, uv.y)
+
+ # update UV coordinate
+ for f in sel_faces:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ diff = uv - origin
+ l[uv_layer].uv = origin + diff * factor
+
+ bmesh.update_edit_mesh(obj.data)
+
+
+class OperatorApplyManual(bpy.types.Operator):
"""
- Operation class: Apply scaled UV
+ Operation class: Apply scaled UV (Manual)
"""
- bl_idname = "uv.muv_wsuv_apply"
- bl_label = "Apply"
- bl_description = "Apply scaled UV based on scale calculation"
+ bl_idname = "uv.muv_world_scale_uv_operator_apply_manual"
+ bl_label = "Apply World Scale UV (Manual)"
+ bl_description = "Apply scaled UV based on user specification"
bl_options = {'REGISTER', 'UNDO'}
+ tgt_density = FloatProperty(
+ name="Density",
+ description="Target Texel Density",
+ default=1.0,
+ min=0.0
+ )
+ tgt_texture_size = IntVectorProperty(
+ name="Texture Size",
+ size=2,
+ min=1,
+ soft_max=10240,
+ default=(1024, 1024),
+ )
origin = EnumProperty(
name="Origin",
description="Aspect Origin",
items=[
- ('CENTER', 'Center', 'Center'),
- ('LEFT_TOP', 'Left Top', 'Left Bottom'),
- ('LEFT_CENTER', 'Left Center', 'Left Center'),
- ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
- ('CENTER_TOP', 'Center Top', 'Center Top'),
- ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
- ('RIGHT_TOP', 'Right Top', 'Right Top'),
- ('RIGHT_CENTER', 'Right Center', 'Right Center'),
- ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
+ ('CENTER', "Center", "Center"),
+ ('LEFT_TOP', "Left Top", "Left Bottom"),
+ ('LEFT_CENTER', "Left Center", "Left Center"),
+ ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+ ('CENTER_TOP', "Center Top", "Center Top"),
+ ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+ ('RIGHT_TOP', "Right Top", "Right Top"),
+ ('RIGHT_CENTER', "Right Center", "Right Center"),
+ ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
],
- default="CENTER"
+ default='CENTER'
)
+ show_dialog = BoolProperty(
+ name="Show Diaglog Menu",
+ description="Show dialog menu if true",
+ default=True,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def __apply_manual(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ tex_size = self.tgt_texture_size
+ uv_area, _, density = measure_wsuv_info(obj, tex_size)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map")
+ return {'CANCELLED'}
+
+ tgt_density = self.tgt_density
+ factor = tgt_density / density
+
+ apply(context.active_object, self.origin, factor)
+ self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+
+ return {'FINISHED'}
def draw(self, _):
layout = self.layout
+ layout.prop(self, "tgt_density")
+ layout.prop(self, "tgt_texture_size")
layout.prop(self, "origin")
+ layout.separator()
+
+ def invoke(self, context, _):
+ if self.show_dialog:
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
+
+ return self.execute(context)
+
def execute(self, context):
- sc = context.scene
+ return self.__apply_manual(context)
+
+
+class OperatorApplyScalingDensity(bpy.types.Operator):
+ """
+ Operation class: Apply scaled UV (Scaling Density)
+ """
+
+ bl_idname = "uv.muv_world_scale_uv_operator_apply_scaling_density"
+ bl_label = "Apply World Scale UV (Scaling Density)"
+ bl_description = "Apply scaled UV with scaling density"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ tgt_scaling_factor = FloatProperty(
+ name="Scaling Factor",
+ default=1.0,
+ max=1000.0,
+ min=0.00001
+ )
+ origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', "Center", "Center"),
+ ('LEFT_TOP', "Left Top", "Left Bottom"),
+ ('LEFT_CENTER', "Left Center", "Left Center"),
+ ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+ ('CENTER_TOP', "Center Top", "Center Top"),
+ ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+ ('RIGHT_TOP', "Right Top", "Right Top"),
+ ('RIGHT_CENTER', "Right Center", "Right Center"),
+ ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+ ],
+ default='CENTER'
+ )
+ src_density = FloatProperty(
+ name="Density",
+ description="Source Texel Density",
+ default=0.0,
+ min=0.0,
+ options={'HIDDEN'}
+ )
+ same_density = BoolProperty(
+ name="Same Density",
+ description="Apply same density",
+ default=False,
+ options={'HIDDEN'}
+ )
+ show_dialog = BoolProperty(
+ name="Show Diaglog Menu",
+ description="Show dialog menu if true",
+ default=True,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def __apply_scaling_density(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
@@ -121,116 +475,172 @@ class MUV_WSUVApply(bpy.types.Operator):
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
- sel_faces = [f for f in bm.faces if f.select]
-
- uv_area, mesh_area, density = measure_wsuv_info(obj)
+ uv_area, _, density = measure_wsuv_info(obj)
if not uv_area:
self.report({'WARNING'},
"Object must have more than one UV map and texture")
return {'CANCELLED'}
- uv_layer = bm.loops.layers.uv.verify()
+ tgt_density = self.src_density * self.tgt_scaling_factor
+ factor = tgt_density / density
- if sc.muv_wsuv_mode == 'PROPORTIONAL':
- tgt_density = sc.muv_wsuv_src_density * sqrt(mesh_area) / \
- sqrt(sc.muv_wsuv_src_mesh_area)
- elif sc.muv_wsuv_mode == 'SCALING':
- tgt_density = sc.muv_wsuv_src_density * sc.muv_wsuv_scaling_factor
- elif sc.muv_wsuv_mode == 'USER':
- tgt_density = sc.muv_wsuv_tgt_density
- elif sc.muv_wsuv_mode == 'CONSTANT':
- tgt_density = sc.muv_wsuv_src_density
+ apply(context.active_object, self.origin, factor)
+ self.report({'INFO'}, "Scaling factor: {0}".format(factor))
- factor = tgt_density / density
+ return {'FINISHED'}
- # calculate origin
- if self.origin == 'CENTER':
- origin = Vector((0.0, 0.0))
- num = 0
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin = origin + uv
- num = num + 1
- origin = origin / num
- elif self.origin == 'LEFT_TOP':
- origin = Vector((100000.0, -100000.0))
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = max(origin.y, uv.y)
- elif self.origin == 'LEFT_CENTER':
- origin = Vector((100000.0, 0.0))
- num = 0
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = origin.y + uv.y
- num = num + 1
- origin.y = origin.y / num
- elif self.origin == 'LEFT_BOTTOM':
- origin = Vector((100000.0, 100000.0))
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = min(origin.y, uv.y)
- elif self.origin == 'CENTER_TOP':
- origin = Vector((0.0, -100000.0))
- num = 0
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = origin.x + uv.x
- origin.y = max(origin.y, uv.y)
- num = num + 1
- origin.x = origin.x / num
- elif self.origin == 'CENTER_BOTTOM':
- origin = Vector((0.0, 100000.0))
- num = 0
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = origin.x + uv.x
- origin.y = min(origin.y, uv.y)
- num = num + 1
- origin.x = origin.x / num
- elif self.origin == 'RIGHT_TOP':
- origin = Vector((-100000.0, -100000.0))
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = max(origin.y, uv.y)
- elif self.origin == 'RIGHT_CENTER':
- origin = Vector((-100000.0, 0.0))
- num = 0
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = origin.y + uv.y
- num = num + 1
- origin.y = origin.y / num
- elif self.origin == 'RIGHT_BOTTOM':
- origin = Vector((-100000.0, 100000.0))
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = min(origin.y, uv.y)
-
- # update UV coordinate
- for f in sel_faces:
- for l in f.loops:
- uv = l[uv_layer].uv
- diff = uv - origin
- l[uv_layer].uv = origin + diff * factor
+ def draw(self, _):
+ layout = self.layout
+
+ layout.label("Source:")
+ col = layout.column()
+ col.prop(self, "src_density")
+ col.enabled = False
+
+ layout.separator()
+
+ if not self.same_density:
+ layout.prop(self, "tgt_scaling_factor")
+ layout.prop(self, "origin")
+
+ layout.separator()
+
+ def invoke(self, context, _):
+ sc = context.scene
+
+ if self.show_dialog:
+ wm = context.window_manager
+
+ if self.same_density:
+ self.tgt_scaling_factor = 1.0
+ else:
+ self.tgt_scaling_factor = \
+ sc.muv_world_scale_uv_tgt_scaling_factor
+ self.src_density = sc.muv_world_scale_uv_src_density
+
+ return wm.invoke_props_dialog(self)
+
+ return self.execute(context)
+
+ def execute(self, context):
+ if self.same_density:
+ self.tgt_scaling_factor = 1.0
+
+ return self.__apply_scaling_density(context)
+
+
+class OperatorApplyProportionalToMesh(bpy.types.Operator):
+ """
+ Operation class: Apply scaled UV (Proportional to mesh)
+ """
+
+ bl_idname = "uv.muv_world_scale_uv_operator_apply_proportional_to_mesh"
+ bl_label = "Apply World Scale UV (Proportional to mesh)"
+ bl_description = "Apply scaled UV proportionaled to mesh"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ origin = EnumProperty(
+ name="Origin",
+ description="Aspect Origin",
+ items=[
+ ('CENTER', "Center", "Center"),
+ ('LEFT_TOP', "Left Top", "Left Bottom"),
+ ('LEFT_CENTER', "Left Center", "Left Center"),
+ ('LEFT_BOTTOM', "Left Bottom", "Left Bottom"),
+ ('CENTER_TOP', "Center Top", "Center Top"),
+ ('CENTER_BOTTOM', "Center Bottom", "Center Bottom"),
+ ('RIGHT_TOP', "Right Top", "Right Top"),
+ ('RIGHT_CENTER', "Right Center", "Right Center"),
+ ('RIGHT_BOTTOM', "Right Bottom", "Right Bottom")
+
+ ],
+ default='CENTER'
+ )
+ src_density = FloatProperty(
+ name="Source Density",
+ description="Source Texel Density",
+ default=0.0,
+ min=0.0,
+ options={'HIDDEN'}
+ )
+ src_uv_area = FloatProperty(
+ name="Source UV Area",
+ description="Source UV Area",
+ default=0.0,
+ min=0.0,
+ options={'HIDDEN'}
+ )
+ src_mesh_area = FloatProperty(
+ name="Source Mesh Area",
+ description="Source Mesh Area",
+ default=0.0,
+ min=0.0,
+ options={'HIDDEN'}
+ )
+ show_dialog = BoolProperty(
+ name="Show Diaglog Menu",
+ description="Show dialog menu if true",
+ default=True,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return is_valid_context(context)
+
+ def __apply_proportional_to_mesh(self, context):
+ obj = context.active_object
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ uv_area, mesh_area, density = measure_wsuv_info(obj)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map and texture")
+ return {'CANCELLED'}
- bmesh.update_edit_mesh(obj.data)
+ tgt_density = self.src_density * sqrt(mesh_area) / sqrt(
+ self.src_mesh_area)
+ factor = tgt_density / density
+
+ apply(context.active_object, self.origin, factor)
self.report({'INFO'}, "Scaling factor: {0}".format(factor))
return {'FINISHED'}
+
+ def draw(self, _):
+ layout = self.layout
+
+ layout.label("Source:")
+ col = layout.column(align=True)
+ col.prop(self, "src_density")
+ col.prop(self, "src_uv_area")
+ col.prop(self, "src_mesh_area")
+ col.enabled = False
+
+ layout.separator()
+ layout.prop(self, "origin")
+
+ layout.separator()
+
+ def invoke(self, context, _):
+ if self.show_dialog:
+ wm = context.window_manager
+ sc = context.scene
+
+ self.src_density = sc.muv_world_scale_uv_src_density
+ self.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
+
+ return wm.invoke_props_dialog(self)
+
+ return self.execute(context)
+
+ def execute(self, context):
+ return self.__apply_proportional_to_mesh(context)