Skip to content

Instantly share code, notes, and snippets.

@batFINGER
Created September 6, 2017 21:57
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save batFINGER/d94ba0cf10321f1ef1cff7ad678d83c3 to your computer and use it in GitHub Desktop.
Save batFINGER/d94ba0cf10321f1ef1cff7ad678d83c3 to your computer and use it in GitHub Desktop.
Mercator UV Projection
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()
@batFINGER
Copy link
Author

mercator

@JotaRata
Copy link

Any upgrade to 2.8x ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment