432 lines
14 KiB
Python
432 lines
14 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
__author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
|
|
__status__ = "production"
|
|
__version__ = "6.6"
|
|
__date__ = "22 Apr 2022"
|
|
|
|
from math import sin, cos, pi
|
|
|
|
import bpy
|
|
import bmesh
|
|
from bpy.props import (
|
|
FloatProperty,
|
|
FloatVectorProperty,
|
|
BoolProperty,
|
|
EnumProperty
|
|
)
|
|
from mathutils import Vector
|
|
|
|
from .. import common
|
|
from ..utils.bl_class_registry import BlClassRegistry
|
|
from ..utils.property_class_registry import PropertyClassRegistry
|
|
from ..utils import compatibility as compat
|
|
|
|
|
|
def _is_valid_context(context):
|
|
# only 'VIEW_3D' space is allowed to execute
|
|
if not common.is_valid_space(context, ['VIEW_3D']):
|
|
return False
|
|
|
|
objs = common.get_uv_editable_objects(context)
|
|
if not objs:
|
|
return False
|
|
|
|
# only edit mode is allowed to execute
|
|
if context.object.mode != 'EDIT':
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _get_uv_layer(ops_obj, bm, assign_uvmap):
|
|
# get UV layer
|
|
if not bm.loops.layers.uv:
|
|
if assign_uvmap:
|
|
bm.loops.layers.uv.new()
|
|
else:
|
|
ops_obj.report({'WARNING'},
|
|
"Object must have more than one UV map")
|
|
return None
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
return uv_layer
|
|
|
|
|
|
def _apply_box_map(bm, uv_layer, size, offset, rotation,
|
|
tex_aspect, force_axis, force_axis_tex_aspect_correction,
|
|
force_axis_rotation):
|
|
scale = 1.0 / size
|
|
|
|
sx = 1.0 * scale
|
|
sy = 1.0 * scale
|
|
sz = 1.0 * scale
|
|
ofx = offset[0]
|
|
ofy = offset[1]
|
|
ofz = offset[2]
|
|
rx = rotation[0] * pi / 180.0
|
|
ry = rotation[1] * pi / 180.0
|
|
rz = rotation[2] * pi / 180.0
|
|
|
|
farx = force_axis_rotation[0] * pi / 180.0
|
|
fary = force_axis_rotation[1] * pi / 180.0
|
|
farz = force_axis_rotation[2] * pi / 180.0
|
|
|
|
sel_faces = [f for f in bm.faces if f.select]
|
|
|
|
# update UV coordinate
|
|
for f in sel_faces:
|
|
n = f.normal
|
|
for l in f.loops:
|
|
co = l.vert.co
|
|
x = co.x * sx
|
|
y = co.y * sy
|
|
z = co.z * sz
|
|
aspect = tex_aspect
|
|
|
|
transformed = False
|
|
if force_axis == 'X':
|
|
# Use Y-plane
|
|
if abs(n[1]) < abs(n[0]) and abs(n[1]) >= abs(n[2]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[1] >= 0.0:
|
|
u = -(x - ofx) * cos(fary) + (z - ofz) * sin(fary)
|
|
v = (x * aspect - ofx) * sin(fary) + \
|
|
(z * aspect - ofz) * cos(fary)
|
|
else:
|
|
u = (x - ofx) * cos(fary) + (z - ofz) * sin(fary)
|
|
v = -(x * aspect - ofx) * sin(fary) + \
|
|
(z * aspect - ofz) * cos(fary)
|
|
transformed = True
|
|
# Use Z-plane
|
|
elif abs(n[2]) < abs(n[0]) and abs(n[2]) >= abs(n[1]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[2] >= 0.0:
|
|
u = (x - ofx) * cos(farz) + (y - ofy) * sin(farz)
|
|
v = -(x * aspect - ofx) * sin(farz) + \
|
|
(y * aspect - ofy) * cos(farz)
|
|
else:
|
|
u = -(x - ofx) * cos(farz) - (y + ofy) * sin(farz)
|
|
v = -(x * aspect + ofx) * sin(farz) + \
|
|
(y * aspect - ofy) * cos(farz)
|
|
transformed = True
|
|
elif force_axis == 'Y':
|
|
# Use X-plane
|
|
if abs(n[0]) < abs(n[1]) and abs(n[0]) >= abs(n[2]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[0] >= 0.0:
|
|
u = (y - ofy) * cos(farx) + (z - ofz) * sin(farx)
|
|
v = -(y * aspect - ofy) * sin(farx) + \
|
|
(z * aspect - ofz) * cos(farx)
|
|
else:
|
|
u = -(y - ofy) * cos(farx) + (z - ofz) * sin(farx)
|
|
v = (y * aspect - ofy) * sin(farx) + \
|
|
(z * aspect - ofz) * cos(farx)
|
|
transformed = True
|
|
# Use Z-plane
|
|
elif abs(n[2]) >= abs(n[0]) and abs(n[2]) < abs(n[1]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[2] >= 0.0:
|
|
u = (x - ofx) * cos(farz) + (y - ofy) * sin(farz)
|
|
v = -(x * aspect - ofx) * sin(farz) + \
|
|
(y * aspect - ofy) * cos(farz)
|
|
else:
|
|
u = -(x - ofx) * cos(farz) - (y + ofy) * sin(farz)
|
|
v = -(x * aspect + ofx) * sin(farz) + \
|
|
(y * aspect - ofy) * cos(farz)
|
|
transformed = True
|
|
elif force_axis == 'Z':
|
|
# Use X-plane
|
|
if abs(n[0]) >= abs(n[1]) and abs(n[0]) < abs(n[2]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[0] >= 0.0:
|
|
u = (y - ofy) * cos(farx) + (z - ofz) * sin(farx)
|
|
v = -(y * aspect - ofy) * sin(farx) + \
|
|
(z * aspect - ofz) * cos(farx)
|
|
else:
|
|
u = -(y - ofy) * cos(farx) + (z - ofz) * sin(farx)
|
|
v = (y * aspect - ofy) * sin(farx) + \
|
|
(z * aspect - ofz) * cos(farx)
|
|
transformed = True
|
|
# Use Y-plane
|
|
elif abs(n[1]) >= abs(n[0]) and abs(n[1]) < abs(n[2]):
|
|
aspect *= force_axis_tex_aspect_correction
|
|
if n[1] >= 0.0:
|
|
u = -(x - ofx) * cos(fary) + (z - ofz) * sin(fary)
|
|
v = (x * aspect - ofx) * sin(fary) + \
|
|
(z * aspect - ofz) * cos(fary)
|
|
else:
|
|
u = (x - ofx) * cos(fary) + (z - ofz) * sin(fary)
|
|
v = -(x * aspect - ofx) * sin(fary) + \
|
|
(z * aspect - ofz) * cos(fary)
|
|
transformed = True
|
|
|
|
if not transformed:
|
|
# X-plane
|
|
if abs(n[0]) >= abs(n[1]) and abs(n[0]) >= abs(n[2]):
|
|
if n[0] >= 0.0:
|
|
u = (y - ofy) * cos(rx) + (z - ofz) * sin(rx)
|
|
v = -(y * aspect - ofy) * sin(rx) + \
|
|
(z * aspect - ofz) * cos(rx)
|
|
else:
|
|
u = -(y - ofy) * cos(rx) + (z - ofz) * sin(rx)
|
|
v = (y * aspect - ofy) * sin(rx) + \
|
|
(z * aspect - ofz) * cos(rx)
|
|
# Y-plane
|
|
elif abs(n[1]) >= abs(n[0]) and abs(n[1]) >= abs(n[2]):
|
|
if n[1] >= 0.0:
|
|
u = -(x - ofx) * cos(ry) + (z - ofz) * sin(ry)
|
|
v = (x * aspect - ofx) * sin(ry) + \
|
|
(z * aspect - ofz) * cos(ry)
|
|
else:
|
|
u = (x - ofx) * cos(ry) + (z - ofz) * sin(ry)
|
|
v = -(x * aspect - ofx) * sin(ry) + \
|
|
(z * aspect - ofz) * cos(ry)
|
|
# Z-plane
|
|
elif abs(n[2]) >= abs(n[0]) and abs(n[2]) >= abs(n[1]):
|
|
if n[2] >= 0.0:
|
|
u = (x - ofx) * cos(rz) + (y - ofy) * sin(rz)
|
|
v = -(x * aspect - ofx) * sin(rz) + \
|
|
(y * aspect - ofy) * cos(rz)
|
|
else:
|
|
u = -(x - ofx) * cos(rz) - (y + ofy) * sin(rz)
|
|
v = -(x * aspect + ofx) * sin(rz) + \
|
|
(y * aspect - ofy) * cos(rz)
|
|
|
|
l[uv_layer].uv = Vector((u, v))
|
|
|
|
|
|
def _apply_planer_map(bm, uv_layer, size, offset, rotation, tex_aspect):
|
|
scale = 1.0 / size
|
|
|
|
sx = 1.0 * scale
|
|
sy = 1.0 * scale
|
|
ofx = offset[0]
|
|
ofy = offset[1]
|
|
rz = rotation * pi / 180.0
|
|
aspect = tex_aspect
|
|
|
|
sel_faces = [f for f in bm.faces if f.select]
|
|
|
|
# calculate average of normal
|
|
n_ave = Vector((0.0, 0.0, 0.0))
|
|
for f in sel_faces:
|
|
n_ave = n_ave + f.normal
|
|
q = n_ave.rotation_difference(Vector((0.0, 0.0, 1.0)))
|
|
|
|
# update UV coordinate
|
|
for f in sel_faces:
|
|
for l in f.loops:
|
|
co = compat.matmul(q, l.vert.co)
|
|
x = co.x * sx
|
|
y = co.y * sy
|
|
|
|
u = x * cos(rz) - y * sin(rz) + ofx
|
|
v = -x * aspect * sin(rz) - y * aspect * cos(rz) + ofy
|
|
|
|
l[uv_layer].uv = Vector((u, v))
|
|
|
|
|
|
@PropertyClassRegistry()
|
|
class _Properties:
|
|
idname = "uvw"
|
|
|
|
@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
|
|
|
|
|
|
@BlClassRegistry()
|
|
@compat.make_annotations
|
|
class MUV_OT_UVW_BoxMap(bpy.types.Operator):
|
|
bl_idname = "uv.muv_uvw_box_map"
|
|
bl_label = "Box Map"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
size = FloatProperty(
|
|
name="Size",
|
|
default=1.0,
|
|
precision=4
|
|
)
|
|
rotation = FloatVectorProperty(
|
|
name="Rotation",
|
|
size=3,
|
|
default=(0.0, 0.0, 0.0),
|
|
subtype='XYZ'
|
|
)
|
|
offset = FloatVectorProperty(
|
|
name="Offset",
|
|
size=3,
|
|
default=(0.0, 0.0, 0.0),
|
|
subtype='XYZ'
|
|
)
|
|
tex_aspect = FloatProperty(
|
|
name="Texture Aspect",
|
|
default=1.0,
|
|
precision=4
|
|
)
|
|
assign_uvmap = BoolProperty(
|
|
name="Assign UVMap",
|
|
description="Assign UVMap when no UVmaps are available",
|
|
default=True
|
|
)
|
|
force_axis = EnumProperty(
|
|
name="Force Axis",
|
|
description="Axis to force the mapping",
|
|
items=[
|
|
('NONE', "None", "None"),
|
|
('X', "X", "Axis X"),
|
|
('Y', "Y", "Axis Y"),
|
|
('Z', "Z", "Axis Z")
|
|
],
|
|
default='NONE'
|
|
)
|
|
force_axis_tex_aspect_correction = FloatProperty(
|
|
name="Texture Aspect Correction (Force Axis)",
|
|
description="Texture Aspect correction for the faces mapped forcibly",
|
|
default=3.14,
|
|
precision=4
|
|
)
|
|
force_axis_rotation = FloatVectorProperty(
|
|
name="Rotation (Force Axis)",
|
|
description="Rotation for the faces mapped forcibly",
|
|
size=3,
|
|
default=(0.0, 0.0, 0.0),
|
|
subtype='XYZ'
|
|
)
|
|
|
|
@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 draw(self, _):
|
|
layout = self.layout
|
|
|
|
col = layout.column()
|
|
row = col.row()
|
|
row.label(text="Size:")
|
|
row.prop(self, "size", text="")
|
|
|
|
layout.label(text="Rotation:")
|
|
layout.row().prop(self, "rotation", text="")
|
|
|
|
layout.label(text="Offset:")
|
|
layout.row().prop(self, "offset", text="")
|
|
|
|
col = layout.column()
|
|
row = col.row()
|
|
row.label(text="Texture Aspect:")
|
|
row.prop(self, "tex_aspect", text="")
|
|
|
|
layout.prop(self, "assign_uvmap")
|
|
|
|
layout.separator(factor=2.0)
|
|
|
|
layout.prop(self, "force_axis")
|
|
if self.force_axis != 'NONE':
|
|
col = layout.column()
|
|
row = col.row()
|
|
row.label(text="Texture Aspect Correction (Force Axis)")
|
|
row.prop(self, "force_axis_tex_aspect_correction", text="")
|
|
|
|
layout.label(text="Rotation (Force Axis)")
|
|
layout.row().prop(self, "force_axis_rotation", text="")
|
|
|
|
def execute(self, context):
|
|
objs = common.get_uv_editable_objects(context)
|
|
|
|
for obj in objs:
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
if common.check_version(2, 73, 0) >= 0:
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# get UV layer
|
|
uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
|
|
if not uv_layer:
|
|
return {'CANCELLED'}
|
|
|
|
_apply_box_map(bm, uv_layer, self.size, self.offset, self.rotation,
|
|
self.tex_aspect, self.force_axis,
|
|
self.force_axis_tex_aspect_correction,
|
|
self.force_axis_rotation)
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
@BlClassRegistry()
|
|
@compat.make_annotations
|
|
class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
|
|
bl_idname = "uv.muv_uvw_best_planer_map"
|
|
bl_label = "Best Planer Map"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
size = FloatProperty(
|
|
name="Size",
|
|
default=1.0,
|
|
precision=4
|
|
)
|
|
rotation = FloatProperty(
|
|
name="Rotation",
|
|
default=0.0
|
|
)
|
|
offset = FloatVectorProperty(
|
|
name="Offset",
|
|
size=2,
|
|
default=(0.0, 0.0)
|
|
)
|
|
tex_aspect = FloatProperty(
|
|
name="Texture Aspect",
|
|
default=1.0,
|
|
precision=4
|
|
)
|
|
assign_uvmap = BoolProperty(
|
|
name="Assign UVMap",
|
|
description="Assign UVMap when no UVmaps are available",
|
|
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):
|
|
objs = common.get_uv_editable_objects(context)
|
|
|
|
for obj in objs:
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
if common.check_version(2, 73, 0) >= 0:
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# get UV layer
|
|
uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
|
|
if not uv_layer:
|
|
return {'CANCELLED'}
|
|
|
|
_apply_planer_map(bm, uv_layer, self.size, self.offset,
|
|
self.rotation, self.tex_aspect)
|
|
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
return {'FINISHED'}
|