File headers: use SPDX license identifiers
[blender-addons.git] / magic_uv / op / texture_wrap.py
blob4f9c868d856a0f3c33064566377313d6b7264bfc
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # <pep8-80 compliant>
5 __author__ = "Nutti <nutti.metro@gmail.com>"
6 __status__ = "production"
7 __version__ = "6.5"
8 __date__ = "6 Mar 2021"
10 import bpy
11 from bpy.props import (
12 BoolProperty,
14 import bmesh
16 from .. import common
17 from ..utils.bl_class_registry import BlClassRegistry
18 from ..utils.property_class_registry import PropertyClassRegistry
21 def _is_valid_context(context):
22 # Multiple objects editing mode is not supported in this feature.
23 objs = common.get_uv_editable_objects(context)
24 if len(objs) != 1:
25 return False
27 # only edit mode is allowed to execute
28 if context.object.mode != 'EDIT':
29 return False
31 # only 'VIEW_3D' space is allowed to execute
32 if not common.is_valid_space(context, ['VIEW_3D']):
33 return False
35 return True
38 @PropertyClassRegistry()
39 class _Properties:
40 idname = "texture_wrap"
42 @classmethod
43 def init_props(cls, scene):
44 class Props():
45 ref_face_index = -1
46 ref_obj = None
48 scene.muv_props.texture_wrap = Props()
50 scene.muv_texture_wrap_enabled = BoolProperty(
51 name="Texture Wrap",
52 description="Texture Wrap is enabled",
53 default=False
55 scene.muv_texture_wrap_set_and_refer = BoolProperty(
56 name="Set and Refer",
57 description="Refer and set UV",
58 default=True
60 scene.muv_texture_wrap_selseq = BoolProperty(
61 name="Selection Sequence",
62 description="Set UV sequentially",
63 default=False
66 @classmethod
67 def del_props(cls, scene):
68 del scene.muv_props.texture_wrap
69 del scene.muv_texture_wrap_enabled
70 del scene.muv_texture_wrap_set_and_refer
71 del scene.muv_texture_wrap_selseq
74 @BlClassRegistry()
75 class MUV_OT_TextureWrap_Refer(bpy.types.Operator):
76 """
77 Operation class: Refer UV
78 """
80 bl_idname = "uv.muv_texture_wrap_refer"
81 bl_label = "Refer"
82 bl_description = "Refer UV"
83 bl_options = {'REGISTER', 'UNDO'}
85 @classmethod
86 def poll(cls, context):
87 # we can not get area/space/region from console
88 if common.is_console_mode():
89 return True
90 return _is_valid_context(context)
92 def execute(self, context):
93 props = context.scene.muv_props.texture_wrap
95 objs = common.get_uv_editable_objects(context)
96 # poll() method ensures that only one object is selected.
97 obj = objs[0]
98 bm = bmesh.from_edit_mesh(obj.data)
99 if common.check_version(2, 73, 0) >= 0:
100 bm.faces.ensure_lookup_table()
102 if not bm.loops.layers.uv:
103 self.report({'WARNING'}, "Object must have more than one UV map")
104 return {'CANCELLED'}
106 sel_faces = [f for f in bm.faces if f.select]
107 if len(sel_faces) != 1:
108 self.report({'WARNING'}, "Must select only one face")
109 return {'CANCELLED'}
111 props.ref_face_index = sel_faces[0].index
112 props.ref_obj = obj
114 return {'FINISHED'}
117 @BlClassRegistry()
118 class MUV_OT_TextureWrap_Set(bpy.types.Operator):
120 Operation class: Set UV
123 bl_idname = "uv.muv_texture_wrap_set"
124 bl_label = "Set"
125 bl_description = "Set UV"
126 bl_options = {'REGISTER', 'UNDO'}
128 @classmethod
129 def poll(cls, context):
130 # we can not get area/space/region from console
131 if common.is_console_mode():
132 return True
133 sc = context.scene
134 props = sc.muv_props.texture_wrap
135 if not props.ref_obj:
136 return False
137 return _is_valid_context(context)
139 def execute(self, context):
140 sc = context.scene
141 props = sc.muv_props.texture_wrap
143 objs = common.get_uv_editable_objects(context)
144 # poll() method ensures that only one object is selected.
145 obj = objs[0]
146 bm = bmesh.from_edit_mesh(obj.data)
147 if common.check_version(2, 73, 0) >= 0:
148 bm.faces.ensure_lookup_table()
150 if not bm.loops.layers.uv:
151 self.report({'WARNING'}, "Object must have more than one UV map")
152 return {'CANCELLED'}
153 uv_layer = bm.loops.layers.uv.verify()
155 if sc.muv_texture_wrap_selseq:
156 sel_faces = []
157 for hist in bm.select_history:
158 if isinstance(hist, bmesh.types.BMFace) and hist.select:
159 sel_faces.append(hist)
160 if not sel_faces:
161 self.report({'WARNING'}, "Must select more than one face")
162 return {'CANCELLED'}
163 else:
164 sel_faces = [f for f in bm.faces if f.select]
165 if len(sel_faces) != 1:
166 self.report({'WARNING'}, "Must select only one face")
167 return {'CANCELLED'}
169 ref_face_index = props.ref_face_index
170 for face in sel_faces:
171 tgt_face_index = face.index
172 if ref_face_index == tgt_face_index:
173 self.report({'WARNING'}, "Must select different face")
174 return {'CANCELLED'}
176 if props.ref_obj != obj:
177 self.report({'WARNING'}, "Object must be same")
178 return {'CANCELLED'}
180 ref_face = bm.faces[ref_face_index]
181 tgt_face = bm.faces[tgt_face_index]
183 # get common vertices info
184 common_verts = []
185 for sl in ref_face.loops:
186 for dl in tgt_face.loops:
187 if sl.vert == dl.vert:
188 info = {"vert": sl.vert, "ref_loop": sl,
189 "tgt_loop": dl}
190 common_verts.append(info)
191 break
193 if len(common_verts) != 2:
194 self.report({'WARNING'},
195 "2 vertices must be shared among faces")
196 return {'CANCELLED'}
198 # get reference other vertices info
199 ref_other_verts = []
200 for sl in ref_face.loops:
201 for ci in common_verts:
202 if sl.vert == ci["vert"]:
203 break
204 else:
205 info = {"vert": sl.vert, "loop": sl}
206 ref_other_verts.append(info)
208 if not ref_other_verts:
209 self.report({'WARNING'}, "More than 1 vertex must be unshared")
210 return {'CANCELLED'}
212 # get reference info
213 ref_info = {}
214 cv0 = common_verts[0]["vert"].co
215 cv1 = common_verts[1]["vert"].co
216 cuv0 = common_verts[0]["ref_loop"][uv_layer].uv
217 cuv1 = common_verts[1]["ref_loop"][uv_layer].uv
218 ov0 = ref_other_verts[0]["vert"].co
219 ouv0 = ref_other_verts[0]["loop"][uv_layer].uv
220 ref_info["vert_vdiff"] = cv1 - cv0
221 ref_info["uv_vdiff"] = cuv1 - cuv0
222 ref_info["vert_hdiff"], _ = common.diff_point_to_segment(
223 cv0, cv1, ov0)
224 ref_info["uv_hdiff"], _ = common.diff_point_to_segment(
225 cuv0, cuv1, ouv0)
227 # get target other vertices info
228 tgt_other_verts = []
229 for dl in tgt_face.loops:
230 for ci in common_verts:
231 if dl.vert == ci["vert"]:
232 break
233 else:
234 info = {"vert": dl.vert, "loop": dl}
235 tgt_other_verts.append(info)
237 if not tgt_other_verts:
238 self.report({'WARNING'}, "More than 1 vertex must be unshared")
239 return {'CANCELLED'}
241 # get target info
242 for info in tgt_other_verts:
243 cv0 = common_verts[0]["vert"].co
244 cv1 = common_verts[1]["vert"].co
245 cuv0 = common_verts[0]["ref_loop"][uv_layer].uv
246 ov = info["vert"].co
247 info["vert_hdiff"], x = common.diff_point_to_segment(
248 cv0, cv1, ov)
249 info["vert_vdiff"] = x - common_verts[0]["vert"].co
251 # calclulate factor
252 fact_h = -info["vert_hdiff"].length / \
253 ref_info["vert_hdiff"].length
254 fact_v = info["vert_vdiff"].length / \
255 ref_info["vert_vdiff"].length
256 duv_h = ref_info["uv_hdiff"] * fact_h
257 duv_v = ref_info["uv_vdiff"] * fact_v
259 # get target UV
260 info["target_uv"] = cuv0 + duv_h + duv_v
262 # apply to common UVs
263 for info in common_verts:
264 info["tgt_loop"][uv_layer].uv = \
265 info["ref_loop"][uv_layer].uv.copy()
266 # apply to other UVs
267 for info in tgt_other_verts:
268 info["loop"][uv_layer].uv = info["target_uv"]
270 common.debug_print("===== Target Other Vertices =====")
271 common.debug_print(tgt_other_verts)
273 bmesh.update_edit_mesh(obj.data)
275 ref_face_index = tgt_face_index
277 if sc.muv_texture_wrap_set_and_refer:
278 props.ref_face_index = tgt_face_index
280 return {'FINISHED'}