Created
September 6, 2017 21:57
-
-
Save batFINGER/d94ba0cf10321f1ef1cff7ad678d83c3 to your computer and use it in GitHub Desktop.
Mercator UV Projection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bl_info = { | |
"name": "Mercator Project", | |
"author": "batFINGER", | |
"version": (1, 0), | |
"blender": (2, 79, 0), | |
"location": "View3D > Mesh > UV UnWrap > Mercator Project", | |
"description": "UV Mercator Projection", | |
"warning": "", | |
"wiki_url": "", | |
"category": "UV", | |
} | |
import bpy | |
import bmesh | |
from bpy.props import FloatProperty | |
from math import sin, log, radians, degrees, inf | |
from mathutils import Vector, Matrix | |
class Spherical: | |
def __init__(self, vert, north=(0, 0, 1), long0=(0, -1)): | |
self.vert = vert | |
self.north, self.long0 = Vector(north), Vector(long0) | |
v = vert.co | |
R = v.length | |
lat = radians(90) - self.north.angle(v) | |
is_pole = v.xy.length < 0.000001 | |
sign = 1 if v.x > 0 else -1 | |
long = 0 if is_pole else sign * self.long0.angle(v.xy) | |
self.R = R | |
self.lat = lat | |
self.long = long | |
self.west = long if long < 0 else long - radians(360) | |
self.east = long if long >= 0 else long + radians(360) | |
def __repr__(self): | |
return "%.3f, %.3f, %.3f" % (self.R, degrees(self.lat), degrees(self.long)) | |
class MercatorUV: | |
def x(self, long): | |
''' mercator longitude mapping ''' | |
return self.R * long | |
def y(self, lat): | |
''' mercator latitude mapping ''' | |
s = sin(lat) | |
return self.R * log((1 + s) / (1 - s)) / 2 | |
def uv(self, face, uv_layer): | |
''' Map a UV face ''' | |
# see if face is east / west | |
orient = "long" | |
c = face.calc_center_median() | |
if c.y > 0: # on the "dark side" | |
orient = "west" if c.x < 0 else "east" | |
for l in face.loops: | |
luv = l[uv_layer] | |
# apply the location of the vertex as a UV | |
p = self.pts[l.vert] | |
lat, long = p.lat, getattr(p, orient) | |
luv.uv = self.scale * (self.translate + Vector([self.x(long), p.y])) | |
def calc_uv(self): | |
bm = self.bm | |
uv_layer = bm.loops.layers.uv.verify() | |
bm.faces.layers.tex.verify() # currently blender needs both layers. | |
bm.select_mode = {'FACE', 'VERT'} | |
bm.select_flush_mode() | |
bm.select_flush(True) | |
# adjust UVs | |
for f in bm.faces: | |
if not f.select: | |
continue | |
self.uv(f, uv_layer) | |
pass | |
def __init__(self, me, bm, minlat, maxlat): | |
def pt(v): | |
s = Spherical(v) | |
v.select = minlat <= s.lat <= maxlat | |
setattr(s, "select", v.select) | |
return s | |
self.minlat, self.maxlat = minlat, maxlat | |
# add a new uv map | |
uv = me.uv_textures.new("Mercator") | |
me.uv_textures.active = uv | |
self.bm = bm | |
# spherical coords for verts | |
self.pts = {v: pt(v) for v in self.bm.verts} | |
# radius average of calc'd s.R | |
self.R = sum(s.R for s in self.pts.values()) / len(self.pts) | |
for s in self.pts.values(): | |
if s.select: | |
# set y on a per verf basis , not per face.verts | |
setattr(s, "y", self.y(s.lat)) | |
# scale UV to [0, 1] make scale matrix | |
scale_x = 1 / (self.R * radians(360)) | |
scale_y = 1 / (self.y(maxlat) - self.y(minlat)) | |
self.scale = Matrix([[scale_x, 0], [0, scale_y]]) | |
# and transform vector | |
self.translate = Vector((self.R * radians(180), -self.y(minlat))) | |
class UV_OT_MercatorProject(bpy.types.Operator): | |
"""Create a Mercator Projection UV Map""" | |
bl_idname = "uv.mercator_project" | |
bl_label = "Mercator Project" | |
bl_options = {'REGISTER', 'UNDO'} | |
minlat = FloatProperty(default=radians(-82), | |
name="Minimum Latitude", | |
description="Minimum Latitude to project.", | |
min=radians(-86), | |
max=radians(86), | |
precision=2, | |
unit='ROTATION') | |
maxlat = FloatProperty(default=radians(82), | |
name="Minimum Latitude", | |
description="Maximum Latitude to project.", | |
min=radians(-86), | |
max=radians(86), | |
precision=2, | |
unit='ROTATION') | |
@classmethod | |
def poll(cls, context): | |
return (context.mode == 'EDIT_MESH') | |
def execute(self, context): | |
obj = context.edit_object | |
me = obj.data | |
bm = bmesh.from_edit_mesh(me) | |
merc = MercatorUV(me, bm, self.minlat, self.maxlat) | |
merc.calc_uv() | |
bmesh.update_edit_mesh(me) | |
return {'FINISHED'} | |
def unwrapmenu(self, context): | |
''' menu item ''' | |
self.layout.operator("uv.mercator_project") | |
def register(): | |
# add to edit mesh > UV menu | |
bpy.types.VIEW3D_MT_uv_map.append(unwrapmenu) | |
bpy.utils.register_class(UV_OT_MercatorProject) | |
def unregister(): | |
bpy.types.VIEW3D_MT_uv_map.remove(unwrapmenu) | |
bpy.utils.unregister_class(UV_OT_MercatorProject) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment