forked from varkenvarken/blenderaddons
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mesh2heightmap.py
139 lines (119 loc) · 4.58 KB
/
mesh2heightmap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# ##### BEGIN GPL LICENSE BLOCK #####
#
# mesh2heightmap.py, a Blender addon
# (c) 2018 Michel J. Anders (varkenvarken)
#
# 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 #####
bl_info = {
"name": "mesh2heightmap",
"author": "Michel Anders (varkenvarken)",
"version": (0, 0, 201812241102),
"blender": (2, 79, 0),
"location": "View3D > Object > Mesh2Heightmap",
"description": "converts a mesh to a heightmap",
"warning": "",
"wiki_url": "https://github.com/varkenvarken/blenderaddons/blob/master/mesh2heightmap.py",
"tracker_url": "",
"category": "Object"}
import numpy as np
import bpy
from bpy.props import BoolProperty, StringProperty, IntProperty
class Mesh2Heightmap(bpy.types.Operator):
"""converts the z-component of a mesh to a heightmap"""
bl_idname = "object.mesh2heightmap"
bl_label = "Mesh2Heightmap"
bl_options = {'REGISTER','UNDO'}
width = IntProperty(name="Width", default=128, min=64, soft_max=2048)
height = IntProperty(name="Height", default=128, min=64, soft_max=2048)
interpolate = BoolProperty(name="Interpolate", default=False)
@classmethod
def poll(cls, context):
return (( context.active_object is not None )
and (type(context.active_object.data) == bpy.types.Mesh)
and (context.active_object.data.uv_layers.active is not None ))
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.prop(self,'width')
col.prop(self,'height')
layout.prop(self,'interpolate')
def execute(self, context):
mesh = context.active_object.data
width, height = self.width, self.height
im = bpy.data.images.new("Heightmap", height, width, float_buffer=True)
# initialize the heightmap to opaque black
hm = np.zeros((width, height, 4), dtype=np.float32)
hm[:,:,3] = 1.0
# get uv coordinates of all loops
uvlayer = mesh.uv_layers.active.data
uv = np.empty(len(uvlayer)*2, dtype=np.float32)
uvlayer.foreach_get('uv',uv)
uv.shape = -1,2
# get the object coordinates of all vertices
co = np.empty(len(mesh.vertices)*3, dtype=
np.float32)
mesh.vertices.foreach_get('co', co)
co.shape = -1,3
# get the vertex indices of all loops
vi = np.empty(len(mesh.loops), dtype=np.int32)
mesh.loops.foreach_get('vertex_index',vi)
# scale the z coordinate of the verts to the range [0,1]
z = co[:,2][vi]
zmax = np.max(z)
zmin = np.min(z)
zd = zmax - zmin
z -= zmin
z /= zd
# scale the uv-coordinates to image dimensions
# (they're still floats!)
# not that we do NOT check if uvs are in range [0,1]
uv[:,0] *= height-1
uv[:,1] *= width-1
uv = np.round(uv).astype(np.int32)
# assign the height to each corresponding map coordinate
# (just the rgb values, we leave alpha as is)
for h,xy in zip(z,uv):
hm[xy[1],xy[0],:3] = h
# perform a linear interpolation for missing rows/columns
# (will occur if map dimension is larger than number of
# vertices in that same dimension)
# NOTE: this will generate incorrect interpolation results
# if the map size is more than twice the size of the grid
# In which case it might be better to generate a map that
# just fits and using an image manipulation program to scale up
if self.interpolate:
uniq0 = np.unique(uv[:,0])
uniq1 = np.unique(uv[:,1])
missing0 = np.setdiff1d(np.arange(height, dtype=np.int32), uniq0)
missing1 = np.setdiff1d(np.arange(height, dtype=np.int32), uniq1)
for y in missing1:
hm[y,:] = (hm[y-1,:] + hm[y-1,:])/2
for x in missing0:
hm[:,x] = (hm[:,x-1] + hm[:,x+1])/2
# copy the map to the new image.
# always this way, i.e. flat copy, because assigning individual
# pixels is way too slow
im.pixels[:] = hm.flat[:]
return {'FINISHED'}
def register():
bpy.utils.register_class(Mesh2Heightmap)
bpy.types.VIEW3D_MT_object.append(menu_func)
def unregister():
bpy.utils.unregister_class(Mesh2Heightmap)
bpy.types.VIEW3D_MT_object.remove(menu_func)
def menu_func(self, context):
self.layout.operator(Mesh2Heightmap.bl_idname, icon='PLUGIN')