mc2mt/blockbuild.py

1544 lines
59 KiB
Python

# 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.
#
# Contributors:
# Originally authored by Acro
# Modified by Phil B
import bpy
from mathutils import *
import os, sys
import sysutil
DEBUG_BBUV = False
DEBUG_SHADER = False
#create class for the other functions? BlockBuilder.
#NICEIF: SpaceView3D.grid_subdivisions = 16 (so they're MC pixel-based)
TERRAIN_TEXTURE_ATLAS_NAME = 'textures_0.png' # based on the filename used by Minecraft (was terrain.png)
TEXTURE_ATLAS_UNITS = 32 # Number of textures x & y in the texture atlas (was 16)
TEXTURE_ATLAS_PIXELS_PER_UNIT = 16
TEXTURE_ATLAS_PIXELS = 512 # Pixel w & h of the texture atlas (TEXTURE_ATLAS_UNITS*TEXTURE_ATLAS_PIXELS_PER_UNIT) (was 256)
def getTextureAtlasU(faceTexId):
return faceTexId % TEXTURE_ATLAS_UNITS
def getTextureAtlasV(faceTexId):
return int(faceTexId / TEXTURE_ATLAS_UNITS) #int division.
def getUVUnit():
#The normalised size of a tx tile within the texture image.
return 1/TEXTURE_ATLAS_UNITS
def isBMesh():
majorver = bpy.app.version[0] * 100 + bpy.app.version[1]
return majorver > 262
#return int(bpy.app.build_revision) > 43451
#class BlockBuilder:
# """Defines methods for creating whole-block Minecraft blocks with correct texturing - just needs minecraft path."""
def construct(blockID, basename, diffuseRGB, cubeTexFaces, extraData, constructType="box", shapeParams=None, cycParams=None):
# find block function/constructor that matches the construct type.
#if it's a simple cube...
#stairs
#onehigh
#torch
block = None
if constructType == 'box':
block = createMCBlock(basename, diffuseRGB, cubeTexFaces, cycParams) #extra data
elif constructType == 'onehigh':
block = createInsetMCBlock(basename, diffuseRGB, cubeTexFaces, [0,15,0], cycParams)
elif constructType == '00track':
block = createTrack(basename, diffuseRGB, cubeTexFaces, extraData, cycParams)
#elif constructType == 'hash': #or crop? Is it the same? crops, etc.
elif constructType == 'cross':
block = createXBlock(basename, diffuseRGB, cubeTexFaces, extraData, cycParams)
elif constructType == 'stair':
block = createStairBlock(basename, diffuseRGB, cubeTexFaces, extraData, cycParams)
elif constructType == 'fence':
block = createFenceBlock(basename, diffuseRGB, cubeTexFaces, shapeParams, cycParams) # for this, shape params will be NESW flags.
elif constructType == 'inset': #make an inset box (requires shapeParams)
block = createInsetMCBlock(basename, diffuseRGB, cubeTexFaces, shapeParams, cycParams) #shapeprms must be a 3-list
else:
block = createMCBlock(basename, diffuseRGB, cubeTexFaces, cycParams) #extra data # soon to be removed as a catch-all!
return block
def getMCTex():
tname = 'mcTexBlocks'
if tname in bpy.data.textures:
return bpy.data.textures[tname]
print("creating fresh new minecraft terrain texture")
texNew = bpy.data.textures.new(tname, 'IMAGE')
texNew.image = getMCImg()
# FIXME
#texNew.image.use_premultiply = True
texNew.image.alpha_mode = 'PREMUL'
texNew.use_alpha = True
texNew.use_preview_alpha = True
texNew.use_interpolation = False
texNew.filter_type = 'BOX' #no AA - nice minecraft pixels!
def getMCImg():
MCPATH = sysutil.getMCPath()
osdir = os.getcwd() #original os folder before jumping to temp.
if TERRAIN_TEXTURE_ATLAS_NAME in bpy.data.images:
return bpy.data.images[TERRAIN_TEXTURE_ATLAS_NAME]
else:
img = None
temppath = os.path.sep.join([MCPATH, TERRAIN_TEXTURE_ATLAS_NAME])
print("Mineblend loading terrain: "+temppath)
if os.path.exists(temppath):
img = bpy.data.images.load(temppath)
else:
# generate a placeholder image for the texture if terrain.png doesn't exist (rather than failing)
print("no terrain texture found... creating empty")
img = bpy.data.images.new(TERRAIN_TEXTURE_ATLAS_NAME, 1024, 1024, True, False)
img.source = 'FILE'
os.chdir(osdir)
return img
def getCyclesMCImg():
#Ideally, we want a very large version of terrain.png to hack around
#cycles' inability to give us control of Alpha in 2.61
#However, for now it just gives a separate instance of the normal one that
#will need to be scaled up manually (ie replace this image to fix all transparent noodles)
#todo: proper interpolation via nodes
if 'hiResTerrain.png' not in bpy.data.images:
im1 = None
if TERRAIN_TEXTURE_ATLAS_NAME not in bpy.data.images:
im1 = getMCImg()
else:
im1 = bpy.data.images[TERRAIN_TEXTURE_ATLAS_NAME]
#Create second version/instance of it.
im2 = im1.copy()
im2.name = 'hiResTerrain.png'
#scale that up / modify... somehow? Add no-interpolation nodes
return bpy.data.images['hiResTerrain.png']
def createBMeshBlockCubeUVs(blockname, me, matrl, faceIndices): #assume me is a cube mesh. RETURNS **NAME** of the uv layer created.
"""Uses faceIndices, a list of per-face MC texture indices, to unwrap
the cube's faces onto their correct places on terrain.png.
Face order for faceIndices is [Bottom,Top,Right,Front,Left,Back]"""
#print("Creating bmesh uvs for: %s" % blockname)
if faceIndices is None:
print("Warning: no face texture for %s" % blockname)
return
__listtype = type([])
if type(faceIndices) != __listtype:
if (type(faceIndices) == type(0)):
faceIndices = [faceIndices]*6
print("Applying singular value to all 6 faces")
else:
print("setting material and uvs for %s: non-numerical face list" % blockname)
print(faceIndices)
raise IndexError("improper face assignment data!")
if matrl.name not in me.materials:
me.materials.append(matrl)
uname = blockname + 'UVs'
if uname in me.uv_textures:
blockUVLayer = me.uv_textures[uname]
else:
blockUVLayer = me.uv_textures.new(name=uname)
#blockUVLoop = me.uv_loop_layers[-1] #works prior to 2.63??
blockUVLoop = me.uv_layers.active
uvData = blockUVLoop.data
#bmesh face indices - a mapping to the new cube order
#faceIndices face order is [Bottom,Top,Right,Front,Left,Back]
#BMESH loop face order is [left,back,right,front,bottom,top] (for default cube)
if DEBUG_BBUV:
print("createBMeshBlockCubeUVs "+blockname+" attempting to get faces: "+str(faceIndices))
bmfi = [faceIndices[4], faceIndices[5], faceIndices[2], faceIndices[3], faceIndices[0], faceIndices[1]]
#get the loop, and iterate it based on the me.polygons face info. yay!
#The order is a bit off from what might be expected, though...
#And the uv order goes uv2 <-- uv1
# | ^
# v |
# uv3 --> uv4
# It's anticlockwise from top right.
#the 4 always-the-same offsets from the uv tile to get its corners
#(anticlockwise from top right).
#TODO: get image dimension to automagically work with hi-res texture packs.
uvUnit = getUVUnit()
#16px is 1/16th of the a 256x256 terrain.png. etc.
#calculation of the tile location will get the top left corner, via "* 16".
# these are the default face uvs, ie topright, topleft, botleft, botright.
uvcorners = [(uvUnit, 0.0), (0.0,0.0), (0.0, -uvUnit), (uvUnit,-uvUnit)]
#uvUnit is subtracted, as Y(v) counts up from image bottom, but I count 0 from top
#top is rotated from default
uvcornersTop = [(uvUnit,-uvUnit), (uvUnit, 0.0), (0.0,0.0), (0.0, -uvUnit)] # 4,1,2,3
#bottom is rotated and flipped from default
uvcornersBot = [(0.0, -uvUnit), (0.0,0.0), (uvUnit, 0.0), (uvUnit,-uvUnit)] # 3,2,1,4
#we have to assign each UV in sequence of the 'loop' for the whole mesh: 24 for a cube.
xim = getMCImg()
meshtexfaces = blockUVLayer.data.values()
matrl.game_settings.alpha_blend = 'CLIP'
matrl.game_settings.use_backface_culling = False
faceNo = 0 #or enumerate me.polygons?
#face order is: [left,back,right,front,bottom,top]
for pface in me.polygons:
face = meshtexfaces[faceNo]
face.image = xim
faceTexId = bmfi[faceNo]
# FIXME - old 16x16 (256x256px) texture map
##calculate the face location on the uvmap
#mcTexU = faceTexId % 16
#mcTexV = int(faceTexId / 16) #int division.
##multiply by square size to get U1,V1 (topleft):
#u1 = (mcTexU * 16.0) / 256.0 # or >> 4 (div by imagesize to get as fraction)
#v1 = (mcTexV * 16.0) / 256.0 # ..
# New 25x19 (512x512px) texture map
mcTexU = getTextureAtlasU(faceTexId)
mcTexV = getTextureAtlasV(faceTexId)
u1 = (mcTexU * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS
v1 = (mcTexV * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS
v1 = 1.0 - v1 #y goes low to high #DEBUG print("That means u1,v1 is %f,%f" % (u1,v1))
##DEBUG
if DEBUG_BBUV:
print("mcTexU,mcTexV "+str(mcTexU)+", "+str(mcTexV)+" - u1, v1 %d,%d" % (u1,v1))
#print("minecraft chunk texture x,y within image: %d,%d" % (mcTexU, mcTexV))
#if DEBUG_BBUV:
# print("createBMeshBlockCubeUVs "+blockname+" u1, v1: %d,%d" % (u1, v1))
loopPolyStart = pface.loop_start #where its verts start in the loop. Yay!
#if loop total's not 4, need to work with ngons or tris or do more complex stuff.
loopPolyCount = pface.loop_total
loopPolyEnd = loopPolyStart + loopPolyCount
corners = uvcorners
if faceNo == 5: #top face
corners = uvcornersTop
elif faceNo == 4: #bottom face
corners = uvcornersBot
uvx = 0
for uvc in range(loopPolyStart, loopPolyEnd):
offset = corners[uvx] # 0..3
mcUV = Vector((u1+offset[0], v1+offset[1]))
#apply the calculated face uv + vert offset to the current loop element
if DEBUG_BBUV:
print("offset "+str(offset)+", mvUV "+str(mcUV))
uvData[uvc].uv = mcUV
uvx += 1
faceNo += 1
me.tessface_uv_textures.data.update() #a guess. does this actually help? YES! Without it all the world's grey and textureless!
return "".join([blockname, 'UVs'])
def createBlockCubeUVs(blockname, me, matrl, faceIndices): #assume me is a cube mesh. RETURNS **NAME** of the uv layer created.
#Use faceIndices, a list of per-face MC texture square indices, to unwrap
#the cube's faces to correct places on terrain.png
if faceIndices is None:
print("Warning: no face texture for %s" % blockname)
return
#Face order is [Bottom,Top,Right,Front,Left,Back]
__listtype = type([])
if type(faceIndices) != __listtype:
if (type(faceIndices) == type(0)):
faceIndices = [faceIndices]*6
print("Applying singular value to all 6 faces")
else:
print("setting material and uvs for %s: non-numerical face list" % blockname)
print(faceIndices)
raise IndexError("improper face assignment data!")
if matrl.name not in me.materials:
me.materials.append(matrl)
uname = blockname + 'UVs'
blockUVLayer = me.uv_textures.new(uname) #assuming it's not so assigned already, ofc.
xim = getMCImg()
meshtexfaces = blockUVLayer.data.values()
#Legacy compatibility feature: before 2.60, the alpha clipping is set not
#via the 'game_settings' but in the material...
bver = bpy.app.version[0] + bpy.app.version[1] / 100.0 #eg 2.59
if bver >= 2.6:
matrl.game_settings.alpha_blend = 'CLIP'
matrl.game_settings.use_backface_culling = False
for fnum, fid in enumerate(faceIndices):
face = meshtexfaces[fnum]
face.image = xim
if bver < 2.6:
face.blend_type = 'ALPHA'
#use_image
#Pick UV square off the 2D texture surface based on its Minecraft texture 'index'
#eg 160 for lapis, 49 for glass, etc.
mcTexU = getTextureAtlasU(fid)
mcTexV = getTextureAtlasV(fid)
#multiply by square size to get U1,V1:
u1 = (mcTexU * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # or >> 4 (div by imagesize to get as fraction)
v1 = (mcTexV * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # ..
v1 = 1.0 - v1 #y goes low to high for some reason.
#DEBUG print("That means u1,v1 is %f,%f" % (u1,v1))
#16px will be 1/16th of the image.
#The image is 256px wide and tall.
uvUnit = getUVUnit()
mcUV1 = Vector((u1,v1))
mcUV2 = Vector((u1+uvUnit,v1))
mcUV3 = Vector((u1+uvUnit,v1-uvUnit)) #subtract uvunit for y
mcUV4 = Vector((u1, v1-uvUnit))
#DEBUG
if DEBUG_BBUV:
print("createBlockCubeUVs Creating UVs for face with values: %f,%f to %f,%f" % (u1,v1,mcUV3[0], mcUV3[1]))
#We assume the cube faces are always the same order.
#So, face 0 is the bottom.
if fnum == 1: # top
face.uv1 = mcUV2
face.uv2 = mcUV1
face.uv3 = mcUV4
face.uv4 = mcUV3
elif fnum == 5: #back
face.uv1 = mcUV1
face.uv2 = mcUV4
face.uv3 = mcUV3
face.uv4 = mcUV2
else: #bottom (0) and all the other sides..
face.uv1 = mcUV3
face.uv2 = mcUV2
face.uv3 = mcUV1
face.uv4 = mcUV4
return "".join([blockname, 'UVs'])
#References for UV stuff:
#http://www.blender.org/forum/viewtopic.php?t=15989&view=previous&sid=186e965799143f26f332f259edd004f4
#newUVs = cubeMesh.uv_textures.new('lapisUVs')
#newUVs.data.values() -> list... readonly?
#contains one item per face...
#each item is a bpy_struct MeshTextureFace
#each has LOADS of options
# .uv1 is a 2D Vector(u,v)
#they go:
# uv1 --> uv2
# |
# V
# uv4 <-- uv3
#
# .. I think
## For comments/explanation, see above.
def createInsetUVs(blockname, me, matrl, faceIndices, insets):
"""Returns name of UV layer created."""
__listtype = type([])
if type(faceIndices) != __listtype:
if (type(faceIndices) == type(0)):
faceIndices = [faceIndices]*6
print("Applying singular value to all 6 faces")
else:
print("setting material and uvs for %s: non-numerical face list" % blockname)
print(faceIndices)
raise IndexError("improper face assignment data!")
#faceindices: array of minecraft material indices into the terrain.png.
#Face order is [Bottom,Top,Right,Front,Left,Back]
uname = blockname + 'UVs'
blockUVLayer = me.uv_textures.new(uname)
xim = getMCImg()
#ADD THE MATERIAL! ...but why not earlier than this? uv layer add first?
if matrl.name not in me.materials:
me.materials.append(matrl)
meshtexfaces = blockUVLayer.data.values()
bver = bpy.app.version[0] + bpy.app.version[1] / 100.0 #eg 2.59
if bver >= 2.6:
matrl.game_settings.alpha_blend = 'CLIP'
matrl.game_settings.use_backface_culling = False
#Insets are [bottom,top,sides]
uvUnit = getUVUnit()
uvPixl = uvUnit / TEXTURE_ATLAS_UNITS
iB = insets[0] * uvPixl
iT = insets[1] * uvPixl
iS = insets[2] * uvPixl
for fnum, fid in enumerate(faceIndices):
face = meshtexfaces[fnum]
face.image = xim
if bver < 2.6:
face.blend_type = 'ALPHA'
#Pick UV square off the 2D texture surface based on its Minecraft index
#eg 160 for lapis, 49 for glass... etc, makes for x,y:
mcTexU = getTextureAtlasU(fid)
mcTexV = getTextureAtlasV(fid)
#DEBUG print("MC chunk tex x,y in image: %d,%d" % (mcTexU, mcTexV))
#multiply by square size to get U1,V1:
u1 = (mcTexU * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # or >> 4 (div by imagesize to get as fraction)
v1 = (mcTexV * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS
v1 = 1.0 - v1 #y goes low to high for some reason. (er...)
#DEBUG print("That means u1,v1 is %f,%f" % (u1,v1))
#16px will be 1/16th of the image.
#The image is 256px wide and tall.
mcUV1 = Vector((u1,v1))
mcUV2 = Vector((u1+uvUnit,v1))
mcUV3 = Vector((u1+uvUnit,v1-uvUnit)) #subtract uvunit for y
mcUV4 = Vector((u1, v1-uvUnit))
#DEBUG print("Creating UVs for face with values: %f,%f to %f,%f" % (u1,v1,mcUV3[0], mcUV3[1]))
#can we assume the cube faces are always the same order? It seems so, yes.
#So, face 0 is the bottom.
if fnum == 0: #bottom
face.uv1 = mcUV3
face.uv2 = mcUV2
face.uv3 = mcUV1
face.uv4 = mcUV4
face.uv3 = Vector((face.uv3[0]+iS, face.uv3[1]-iS))
face.uv2 = Vector((face.uv2[0]-iS, face.uv2[1]-iS))
face.uv1 = Vector((face.uv1[0]-iS, face.uv1[1]+iS))
face.uv4 = Vector((face.uv4[0]+iS, face.uv4[1]+iS))
elif fnum == 1: # top
face.uv1 = mcUV2
face.uv2 = mcUV1
face.uv3 = mcUV4
face.uv4 = mcUV3
#do insets! OMG, they really ARE anticlockwise. ffs.
#why wasn't it right the very, very first time?!
## Nope. This is messed up. The error is endemic and spread
#through all uv application in this script.
#vertex ordering isn't the problem, script references have
#confused the entire issue.
# uv1(2)-> uv2 (1)
# |
# V
# uv4(3) <-- uv3(4)
face.uv2 = Vector((face.uv2[0]+iS, face.uv2[1]-iS))
face.uv1 = Vector((face.uv1[0]-iS, face.uv1[1]-iS))
face.uv4 = Vector((face.uv4[0]-iS, face.uv4[1]+iS))
face.uv3 = Vector((face.uv3[0]+iS, face.uv3[1]+iS))
elif fnum == 5: #back
face.uv1 = mcUV1
face.uv2 = mcUV4
face.uv3 = mcUV3
face.uv4 = mcUV2
face.uv1 = Vector((face.uv1[0]+iS, face.uv1[1]-iT))
face.uv4 = Vector((face.uv4[0]-iS, face.uv4[1]-iT))
face.uv3 = Vector((face.uv3[0]-iS, face.uv3[1]+iB))
face.uv2 = Vector((face.uv2[0]+iS, face.uv2[1]+iB))
else: #all the other sides..
face.uv1 = mcUV3
face.uv2 = mcUV2
face.uv3 = mcUV1
face.uv4 = mcUV4
face.uv3 = Vector((face.uv3[0]+iS, face.uv3[1]-iT))
face.uv2 = Vector((face.uv2[0]-iS, face.uv2[1]-iT))
face.uv1 = Vector((face.uv1[0]-iS, face.uv1[1]+iB))
face.uv4 = Vector((face.uv4[0]+iS, face.uv4[1]+iB))
return "".join([blockname, 'UVs'])
def createBMeshInsetUVs(blockname, me, matrl, faceIndices, insets):
"""Uses faceIndices, a list of per-face MC texture indices, to unwrap
the cube's faces onto their correct places on terrain.png.
Uses 3 insets ([bottom,top,sides]) to indent UVs per-face.
Face order for faceIndices is [Bottom,Top,Right,Front,Left,Back]"""
#print("Creating bmesh uvs for: %s" % blockname)
if faceIndices is None:
print("Warning: no face texture for %s" % blockname)
return
__listtype = type([])
if type(faceIndices) != __listtype:
if (type(faceIndices) == type(0)):
faceIndices = [faceIndices]*6
print("Applying singular value to all 6 faces")
else:
print("setting material and uvs for %s: non-numerical face list" % blockname)
print(faceIndices)
raise IndexError("improper face assignment data!")
if matrl.name not in me.materials:
me.materials.append(matrl)
uname = blockname + 'UVs'
if uname in me.uv_textures:
blockUVLayer = me.uv_textures[uname]
else:
blockUVLayer = me.uv_textures.new(name=uname)
#blockUVLoop = me.uv_loop_layers[-1] #Works prior to 2.63! no it doesn't!!
blockUVLoop = me.uv_layers.active
uvData = blockUVLoop.data
bmfi = [faceIndices[4], faceIndices[5], faceIndices[2], faceIndices[3], faceIndices[0], faceIndices[1]]
uvUnit = getUVUnit()
#Insets are [bottom,top,sides]
uvPixl = uvUnit / TEXTURE_ATLAS_UNITS
iB = insets[0] * uvPixl #insetBottom
iT = insets[1] * uvPixl #insetTop
iS = insets[2] * uvPixl #insetSides
#Sorry. This array set is going to be dense, horrible, and impenetrable.
#For the simple version of this, see createBMeshUVs, not the insets one
#uvcorners is for sides. Xvalues affected by iS
uvcorners = [(uvUnit-iS, 0.0-iT), (0.0+iS,0.0-iT), (0.0+iS, -uvUnit+iB), (uvUnit-iS,-uvUnit+iB)]
uvcornersTop = [(uvUnit-iS,-uvUnit+iS), (uvUnit-iS, 0.0-iS), (0.0+iS,0.0-iS), (0.0+iS, -uvUnit+iS)] # 4,1,2,3
uvcornersBot = [(0.0+iS, -uvUnit+iS), (0.0+iS,0.0-iS), (uvUnit-iS, 0.0-iS), (uvUnit-iS,-uvUnit+iS)] # 3,2,1,4
xim = getMCImg()
meshtexfaces = blockUVLayer.data.values()
matrl.game_settings.alpha_blend = 'CLIP'
matrl.game_settings.use_backface_culling = False
faceNo = 0 #or enumerate me.polygons?
#face order is: [left,back,right,front,bottom,top]
for pface in me.polygons:
face = meshtexfaces[faceNo]
face.image = xim
faceTexId = bmfi[faceNo]
#calculate the face location on the uvmap
mcTexU = getTextureAtlasU(faceTexId)
mcTexV = getTextureAtlasV(faceTexId)
#DEBUG
if DEBUG_BBUV:
print("createBMeshInsetUVs minecraft chunk texture x,y within image: %d,%d" % (mcTexU, mcTexV))
#multiply by square size to get U1,V1 (topleft):
u1 = (mcTexU * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # or >> 4 (div by imagesize to get as fraction)
v1 = (mcTexV * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # ..
v1 = 1.0 - v1 #y goes low to high #DEBUG print("That means u1,v1 is %f,%f" % (u1,v1))
loopPolyStart = pface.loop_start #where its verts start in the loop. Yay!
#if loop total's not 4, need to work with ngons or tris or do more complex stuff.
loopPolyCount = pface.loop_total
loopPolyEnd = loopPolyStart + loopPolyCount
corners = uvcorners
if faceNo == 5: #top face
corners = uvcornersTop
elif faceNo == 4: #bottom face
corners = uvcornersBot
uvx = 0
for uvc in range(loopPolyStart, loopPolyEnd):
offset = corners[uvx] # 0..3
mcUV = Vector((u1+offset[0], v1+offset[1]))
#apply the calculated face uv + vert offset to the current loop element
uvData[uvc].uv = mcUV
uvx += 1
faceNo += 1
me.tessface_uv_textures.data.update() #Without this, all the world is grey and textureless!
return "".join([blockname, 'UVs'])
# Cycles materials. createNGmc* are for Node Groups and create*CyclesMat are the for the materials that use them.
#
# Aside from simplifying the individual material layouts, the reason for using Node Groups extensively is to allow for users to easily customize the overall look of their scene (i.e. rather than having to modify dozens of materials, some changes can have global effect by modifying a single Node Group, depending on the change desired)
MC_SHADER_TEX="mcShaderTex"
MC_SHADER_DIFFUSE="mcShaderDiffuse"
MC_SHADER_STENCIL="mcShaderStencil"
MC_SHADER_STENCIL_COLORED="mcShaderStencilColored"
MC_GROUP_TEX_OUTPUT="Color"
MC_GROUP_DIFFUSE_OUTPUT="BSDF"
MC_GROUP_STENCIL_OUTPUT="Shader"
MC_GROUP_STENCIL_COLORED_OUTPUT="Shader"
TYPE_NODE_GROUP_INPUT="NodeGroupInput"
TYPE_NODE_GROUP_OUTPUT="NodeGroupOutput"
TYPE_NODE_GROUP="ShaderNodeGroup"
BSDF_OUTPUT="BSDF"
FACTOR_INPUT="Fac"
def createNGmcTexture():
"""Node Group for texture. This is a simple texture atlas mapping"""
if DEBUG_SHADER:
print("createNGmcTexture")
ng = bpy.data.node_groups.new(MC_SHADER_TEX,"ShaderNodeTree")
ngo = ng.nodes.new(type=TYPE_NODE_GROUP_OUTPUT)
texCoord = ng.nodes.new(type="ShaderNodeTexCoord")
imageTex = ng.nodes.new(type="ShaderNodeTexImage")
imageTex.image = getMCImg()
imageTex.interpolation = "Closest"
ng.links.new(imageTex.inputs[0],texCoord.outputs[2]) # link the texCoord uv to the imageTex vector
ng.links.new(ngo.inputs[0],imageTex.outputs[0])
ng.links.new(ngo.inputs[1],imageTex.outputs[1])
texCoord.location = Vector((-200, 200))
imageTex.location = Vector((0, 200))
ngo.location = Vector((200, 200))
def setNodeGroup(node,ngName):
if DEBUG_SHADER:
print("setNodeGroup: "+ngName)
# FIXME - is there a better way to use a node group from within a node group?
node.name=ngName
node.label=ngName
node.node_tree=bpy.data.node_groups[ngName]
def createNGmcDiffuse():
"""Node Group for diffuse materials"""
if DEBUG_SHADER:
print("createNGmcDiffuse")
ng = bpy.data.node_groups.new(MC_SHADER_DIFFUSE,"ShaderNodeTree")
ngo = ng.nodes.new(type=TYPE_NODE_GROUP_OUTPUT)
tex = ng.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(tex,MC_SHADER_TEX)
diffuse = ng.nodes.new(type="ShaderNodeBsdfDiffuse")
ng.links.new(diffuse.inputs[0],tex.outputs[0]) # link the texCoord uv to the imageTex vector
ng.links.new(ngo.inputs[0],diffuse.outputs[0])
ng.links.new(ngo.inputs[1],tex.outputs[1]) # For stained glass etc
tex.location = Vector((0, 0))
diffuse.location = Vector((200, 200))
ngo.location = Vector((400, 0))
#def createNGmcStencil(): # FIXME - how to handle alternate node data flows? (i.e. loopback / inner node group issue)
# ng = bpy.data.node_groups.new(MC_SHADER_STENCIL,"ShaderNodeTree")
# ngo = ng.nodes.new(type="NodeGroupOutput")
# ngi = ng.nodes.new(type="NodeGroupInput")
# diffNode = ng.nodes.new(type="ShaderNodeGroup")
# setNodeGroup(diffNode,MC_SHADER_DIFFUSE)
#
# links = ng.links
#
# rgbtobwNode = ng.nodes.new(type="ShaderNodeRGBToBW")
# gtNode = ng.nodes.new(type="ShaderNodeMath")
# gtNode.name = "AlphaBlackGT"
# gtNode.operation = 'GREATER_THAN'
# gtNode.inputs[0].default_value = 0.001
#
# transpNode = ng.nodes.new(type="ShaderNodeBsdfTransparent")
# mixNode = ng.nodes.new(type="ShaderNodeMixShader")
#
# ngi.location = Vector((-200,0))
# diffNode.location = Vector((0,0))
# rgbtobwNode.location = Vector((200,200))
# gtNode.location = Vector((400,200))
# transpNode.location = Vector((400,-200))
# mixNode.location = Vector((600,0))
# ngo.location = Vector((800,0))
#
# links.new(input=diffNode.outputs[MC_GROUP_DIFFUSE_OUTPUT], output=rgbtobwNode.inputs['Color'])
# links.new(input=rgbtobwNode.outputs['Val'], output=gtNode.inputs[1])
# links.new(input=gtNode.outputs['Value'], output=mixNode.inputs['Fac'])
#
# #links.new(input=diffNode.outputs[MC_GROUP_DIFFUSE_OUTPUT], output=diff2Node.inputs['Color'])
# #links.new(input=diff2Node.outputs['BSDF'], output=mixNode.inputs[1])
#
# # leave the options open
# #links.new(input=diffNode.outputs['BSDF'], output=mixNode.inputs[1])
#
# links.new(input=transpNode.outputs['BSDF'], output=mixNode.inputs[2])
#
# links.new(input=ngi.outputs[0], output=mixNode.inputs[1])
#
# #links.new(input=mixNode.outputs['Shader'], output=ngo.inputs['Surface'])
# links.new(input=mixNode.outputs['Shader'], output=ngo.inputs[0])
# links.new(input=diffNode.outputs['BSDF'], output=ngo.inputs[1])
def createNGmcStencil():
"""Node Group for stencil materials (i.e. colored textures with alpha)"""
if DEBUG_SHADER:
print("createNGmcStencil")
ng = bpy.data.node_groups.new(MC_SHADER_STENCIL,"ShaderNodeTree")
ngo = ng.nodes.new(type=TYPE_NODE_GROUP_OUTPUT)
ngi = ng.nodes.new(type=TYPE_NODE_GROUP_INPUT)
inNode = ng.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(inNode,MC_SHADER_TEX)
links = ng.links
diffNode = ng.nodes.new(type="ShaderNodeBsdfDiffuse")
rgbtobwNode = ng.nodes.new(type="ShaderNodeRGBToBW")
gtNode = ng.nodes.new(type="ShaderNodeMath")
gtNode.name = "AlphaBlackGT"
gtNode.operation = 'GREATER_THAN'
gtNode.inputs[0].default_value = 0.001
transpNode = ng.nodes.new(type="ShaderNodeBsdfTransparent")
mixNode = ng.nodes.new(type="ShaderNodeMixShader")
ngi.location = Vector((-200,0))
inNode.location = Vector((0,0))
rgbtobwNode.location = Vector((200,200))
diffNode.location = Vector((200,0))
gtNode.location = Vector((400,200))
transpNode.location = Vector((400,-200))
mixNode.location = Vector((600,0))
ngo.location = Vector((800,0))
links.new(input=inNode.outputs[MC_GROUP_TEX_OUTPUT], output=rgbtobwNode.inputs['Color'])
links.new(input=rgbtobwNode.outputs['Val'], output=gtNode.inputs[1])
links.new(input=gtNode.outputs['Value'], output=mixNode.inputs[FACTOR_INPUT])
# leave the options open
links.new(input=inNode.outputs[MC_GROUP_TEX_OUTPUT], output=diffNode.inputs['Color'])
links.new(input=diffNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[1])
links.new(input=transpNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[2])
links.new(input=mixNode.outputs['Shader'], output=ngo.inputs[0])
def createNGmcStencilColored():
"""Node Group for colored stencil materials (i.e. grey scale texture with alpha that needs to be colored)"""
if DEBUG_SHADER:
print("createNGmcStencilColored")
ng = bpy.data.node_groups.new(MC_SHADER_STENCIL_COLORED,"ShaderNodeTree")
ngo = ng.nodes.new(type=TYPE_NODE_GROUP_OUTPUT)
ngi = ng.nodes.new(type=TYPE_NODE_GROUP_INPUT)
texNode = ng.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(texNode,MC_SHADER_TEX)
links = ng.links
rgbtobwNode = ng.nodes.new(type="ShaderNodeRGBToBW")
gtNode = ng.nodes.new(type="ShaderNodeMath")
gtNode.name = "AlphaBlackGT"
gtNode.operation = 'GREATER_THAN'
gtNode.inputs[0].default_value = 0.001
transpNode = ng.nodes.new(type="ShaderNodeBsdfTransparent")
alphaMixNode = ng.nodes.new(type="ShaderNodeMixShader")
ngi.location = Vector((-200,0))
texNode.location = Vector((0,0))
rgbtobwNode.location = Vector((200,200))
gtNode.location = Vector((400,200))
transpNode.location = Vector((400,-200))
alphaMixNode.location = Vector((600,0))
ngo.location = Vector((800,0))
links.new(input=texNode.outputs[MC_GROUP_TEX_OUTPUT], output=rgbtobwNode.inputs['Color'])
links.new(input=rgbtobwNode.outputs['Val'], output=gtNode.inputs[1])
links.new(input=gtNode.outputs['Value'], output=alphaMixNode.inputs[FACTOR_INPUT])
links.new(input=transpNode.outputs[BSDF_OUTPUT], output=alphaMixNode.inputs[2])
links.new(input=alphaMixNode.outputs['Shader'], output=ngo.inputs[0])
## 'colored' specific portion of material
colorMixNode = ng.nodes.new(type="ShaderNodeMixRGB")
#colorMixNode.inputs[1].name="Dark color"
#colorMixNode.inputs[2].name="Light color"
colorDiffNode = ng.nodes.new(type="ShaderNodeBsdfDiffuse")
colorMixNode.location = Vector((0,400))
colorDiffNode.location = Vector((200,400))
links.new(input=rgbtobwNode.outputs['Val'], output=colorMixNode.inputs[FACTOR_INPUT])
# FIXME - material will not render correctly when names are set (i.e. even though the viewport looks fine, the rgb color mix needs to be re-added and links re-established for successful (i.e. non-black) render.)
#colorMixNode.inputs[1].name="Dark color"
#colorMixNode.inputs[2].name="Light color"
#links.new(input=ngi.outputs[0], output=colorMixNode.inputs["Dark color"])
#links.new(input=ngi.outputs[1], output=colorMixNode.inputs["Light color"])
links.new(input=ngi.outputs[0], output=colorMixNode.inputs[1])
links.new(input=ngi.outputs[1], output=colorMixNode.inputs[2])
links.new(input=colorMixNode.outputs["Color"], output=colorDiffNode.inputs["Color"])
links.new(input=colorDiffNode.outputs[BSDF_OUTPUT], output=alphaMixNode.inputs[1])
ngi.outputs[1].name="Dark color"
ngi.outputs[2].name="Light color"
def createNodeGroups():
"""Create node groups if they don't already exist"""
if DEBUG_SHADER:
print("createNodeGroups")
existsNode = bpy.data.node_groups.get(MC_SHADER_DIFFUSE)
if existsNode==None:
createNGmcTexture()
createNGmcDiffuse()
createNGmcStencil()
createNGmcStencilColored()
def removeExistingDiffuseNode(ntree):
olddif = ntree.nodes['Diffuse BSDF']
ntree.nodes.remove(olddif)
def createDiffuseCyclesMat(mat):
"""Create a basic textured, diffuse material that uses existing UV mapping into texture atlas"""
if DEBUG_SHADER:
print("createDiffuseCyclesMat")
#compatibility with Blender 2.5x:
if not hasattr(bpy.context.scene, 'cycles'):
print("No cycles support... skipping")
return
#Switch render engine to Cycles. Yippee ki-yay!
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
#maybe check number of nodes - there should be 2.
ntree = mat.node_tree
mcdif = ntree.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(mcdif,MC_SHADER_DIFFUSE)
removeExistingDiffuseNode(ntree)
matOutNode = ntree.nodes['Material Output']
ntree.links.new(input=mcdif.outputs[MC_GROUP_DIFFUSE_OUTPUT], output=matOutNode.inputs['Surface'])
mcdif.location = Vector((0,0))
matOutNode.location = Vector((200,0))
def createEmissionCyclesMat(mat, emitAmt):
"""Emissive materials such as lava, glowstone, etc"""
if DEBUG_SHADER:
print("createEmissionCyclesMat")
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
ntree = mat.node_tree #there will now be 4 nodes in there, one of them being the diffuse shader.
removeExistingDiffuseNode(ntree)
diffNode = ntree.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(diffNode,MC_SHADER_TEX)
matNode = ntree.nodes['Material Output']
emitNode = ntree.nodes.new(type='ShaderNodeEmission')
nodes = ntree.nodes
links = ntree.links
diffNode.location = Vector((0,0))
emitNode.location = Vector((200,0))
matNode.location = Vector((400,0))
#change links: delete the old links and add new ones.
emitNode.inputs['Strength'].default_value = float(emitAmt) #set this from the EMIT value of data passed in.
bsdfDiffSockOut = diffNode.outputs[MC_GROUP_TEX_OUTPUT]
emitSockOut = emitNode.outputs[0]
for nl in links:
print("link "+str(nl))
if nl.to_socket == matNode:
links.remove(nl)
links.new(input=diffNode.outputs[0], output=emitNode.inputs[0])
links.new(input=emitNode.outputs[0], output=matNode.inputs['Surface'])
def createStencilCyclesMat(mat):
"""Stencil materials such as flowers"""
if DEBUG_SHADER:
print("createStencilCyclesMat")
#Ensure Cycles is in use
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
ntree = mat.node_tree
nodes = ntree.nodes
links = ntree.links
inNode = ntree.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(inNode,MC_SHADER_TEX)
diffNode = nodes["Diffuse BSDF"] # reuse the one that already exists
matNode = nodes['Material Output']
rgbtobwNode = ntree.nodes.new(type="ShaderNodeRGBToBW")
gtNode = ntree.nodes.new(type="ShaderNodeMath")
gtNode.name = "AlphaBlackGT"
gtNode.operation = 'GREATER_THAN'
gtNode.inputs[0].default_value = 0.001
transpNode = ntree.nodes.new(type="ShaderNodeBsdfTransparent")
mixNode = ntree.nodes.new(type="ShaderNodeMixShader")
inNode.location = Vector((0,0))
diffNode.location = Vector((200,0))
rgbtobwNode.location = Vector((200,200))
gtNode.location = Vector((400,200))
transpNode.location = Vector((400,-200))
mixNode.location = Vector((600,0))
matNode.location = Vector((800,0))
for nl in links:
if nl.to_socket == matNode:
links.remove(nl)
links.new(input=inNode.outputs[MC_GROUP_TEX_OUTPUT], output=rgbtobwNode.inputs['Color'])
links.new(input=rgbtobwNode.outputs['Val'], output=gtNode.inputs[1])
links.new(input=gtNode.outputs['Value'], output=mixNode.inputs[FACTOR_INPUT])
links.new(input=inNode.outputs[MC_GROUP_TEX_OUTPUT], output=diffNode.inputs["Color"])
links.new(input=diffNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[1])
links.new(input=transpNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[2])
links.new(input=mixNode.outputs['Shader'], output=matNode.inputs['Surface'])
def createLeafCyclesMat(mat):
"""Colored stencil materials such as leaves"""
if DEBUG_SHADER:
print("createLeafCyclesMat")
"""Very similar to the transparent (glass) material but different enough to need its own"""
#Ensure Cycles is in use
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
ntree = mat.node_tree #there will now be 4 nodes in there, one of them being the diffuse shader.
removeExistingDiffuseNode(ntree)
nodes = ntree.nodes
links = ntree.links
stencilNode = ntree.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(stencilNode,MC_SHADER_STENCIL_COLORED)
matNode = nodes['Material Output']
darkColorNode = ntree.nodes.new(type="ShaderNodeRGB")
darkColorNode.outputs[0].default_value = (0.01, 0.0185002, 0.0137021, 1)
lightColorNode = ntree.nodes.new(type="ShaderNodeRGB")
lightColorNode.outputs[0].default_value = (0.098, 0.238398, 0.135633, 1)
darkColorNode.location = Vector((0, 200))
lightColorNode.location = Vector((0, 0))
stencilNode.location = Vector((400,0))
matNode.location = Vector((600,0))
links.new(input=darkColorNode.outputs['Color'], output=stencilNode.inputs[0])
links.new(input=lightColorNode.outputs['Color'], output=stencilNode.inputs[1])
links.new(input=stencilNode.outputs['Shader'], output=matNode.inputs['Surface'])
def createLeafCyclesMatOld(mat):
"""Very similar to the transparent (glass) material but different enough to need its own"""
if DEBUG_SHADER:
print("createLeafCyclesMat")
#Ensure Cycles is in use
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
ntree = mat.node_tree #there will now be 4 nodes in there, one of them being the diffuse shader.
nodes = ntree.nodes
links = ntree.links
removeExistingDiffuseNode(ntree)
diffNode = ntree.nodes.new(type=TYPE_NODE_GROUP)
setNodeGroup(diffNode,MC_SHADER_TEX)
matNode = nodes['Material Output']
rgbtobwNode = ntree.nodes.new(type="ShaderNodeRGBToBW")
gtNode = ntree.nodes.new(type="ShaderNodeMath")
gtNode.name = "AlphaBlackGT"
gtNode.operation = 'GREATER_THAN'
gtNode.inputs[0].default_value = 0.001
transpNode = ntree.nodes.new(type="ShaderNodeBsdfTransparent")
mixNode = ntree.nodes.new(type="ShaderNodeMixShader")
diffNode.location = Vector((0,0))
rgbtobwNode.location = Vector((200,200))
gtNode.location = Vector((400,200))
transpNode.location = Vector((400,-200))
mixNode.location = Vector((600,0))
matNode.location = Vector((800,0))
for nl in links:
if nl.to_socket == matNode:
links.remove(nl)
links.new(input=diffNode.outputs[MC_GROUP_TEX_OUTPUT], output=rgbtobwNode.inputs['Color'])
links.new(input=rgbtobwNode.outputs['Val'], output=gtNode.inputs[1])
links.new(input=gtNode.outputs['Value'], output=mixNode.inputs[FACTOR_INPUT])
# Leaf difference: feed the matl color into transparent... not needed? FIXME
links.new(input=transpNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[2])
links.new(input=mixNode.outputs['Shader'], output=matNode.inputs['Surface'])
# Leaf specific portion of material
lcrampNode = ntree.nodes.new(type="ShaderNodeValToRGB")
lcrampNode.color_ramp.elements[1].color = (0.098, 0.238398, 0.135633, 1)
lcrampNode.color_ramp.elements[0].color = (0.01, 0.0185002, 0.0137021, 1)
ldiffNode = ntree.nodes.new(type="ShaderNodeBsdfDiffuse")
lcrampNode.location = Vector((400,500))
ldiffNode.location = Vector((700,500))
links.new(input=rgbtobwNode.outputs['Val'], output=lcrampNode.inputs[FACTOR_INPUT])
links.new(input=lcrampNode.outputs['Color'], output=ldiffNode.inputs['Color'])
links.new(input=ldiffNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[1])
def createPlainAlphaCyclesMat(mat):
"""Partially transparent materials such as stained glass"""
if DEBUG_SHADER:
print("createPlainAlphaCyclesMat")
#Ensure Cycles is in use
if bpy.context.scene.render.engine != 'CYCLES':
bpy.context.scene.render.engine = 'CYCLES'
mat.use_nodes = True
ntree = mat.node_tree
nodes = ntree.nodes
createDiffuseCyclesMat(mat)
diffNode = nodes[MC_SHADER_DIFFUSE]
matNode = nodes['Material Output']
transpNode = ntree.nodes.new(type="ShaderNodeBsdfTransparent")
mixNode = ntree.nodes.new(type="ShaderNodeMixShader")
diffNode.location = Vector((0,0))
transpNode.location = Vector((200,-200))
mixNode.location = Vector((400,0))
matNode.location = Vector((600,0))
links = ntree.links
# FIXME - does reversing order of inputs give the right effect for stained glass?
links.new(input=diffNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[2])
links.new(input=diffNode.outputs['Alpha'], output=mixNode.inputs[FACTOR_INPUT])
links.new(input=transpNode.outputs[BSDF_OUTPUT], output=mixNode.inputs[1])
links.new(input=mixNode.outputs['Shader'], output=matNode.inputs['Surface'])
def setupCyclesMat(material, cyclesParams):
if DEBUG_SHADER:
print("setupCyclesMat")
createNodeGroups()
if 'emit' in cyclesParams:
emitAmt = cyclesParams['emit']
if emitAmt > 0.0:
createEmissionCyclesMat(material, emitAmt)
return
if 'stencil' in cyclesParams and cyclesParams['stencil']: #must be boolean true
if 'ovr' in cyclesParams:
#get the overlay colour, and create a transp overlay material.
return
#not overlay
createStencilCyclesMat(material)
return
if 'alpha' in cyclesParams and cyclesParams['alpha']:
createPlainAlphaCyclesMat(material)
return
if 'leaf' in cyclesParams and cyclesParams['leaf']:
createLeafCyclesMat(material)
return
createDiffuseCyclesMat(material)
def getMCMat(blocktype, rgbtriple, cyclesParams=None): #take cycles params Dictionary - ['type': DIFF/EMIT/TRANSP, 'emitAmt': 0.0]
"""Creates or returns a general-use default Minecraft material."""
matname = 'mc' + blocktype + 'Mat'
if matname in bpy.data.materials:
return bpy.data.materials[matname]
blockMat = bpy.data.materials.new(matname)
## ALL-MATERIAL DEFAULTS
blockMat.use_transparency = True # surely not for everything!? not stone,dirt,etc!
blockMat.alpha = 0.0
blockMat.specular_alpha = 0.0
blockMat.specular_intensity = 0.0
##TODO: blockMat.use_transparent_shadows - on recving objects (solids)
##TODO: Cast transparent shadows from translucent things like water.
if rgbtriple is not None:
#create the solid shaded-view material colour
diffusecolour = [n/256.0 for n in rgbtriple]
blockMat.diffuse_color = diffusecolour
blockMat.diffuse_shader = 'OREN_NAYAR'
blockMat.diffuse_intensity = 0.8
blockMat.roughness = 0.909
else:
#create a blank/obvious 'unhelpful' material.
blockMat.diffuse_color = [214,127,255] #shocking pink
return blockMat
###############################################################################
# Primary Block-Shape Creation Functions #
###############################################################################
def createCubeMesh():
bpy.context.scene.cursor_location = (0.0, 0.0, 0.0)
bpy.ops.mesh.primitive_cube_add()
blockOb = bpy.context.object
bpy.ops.transform.resize(value=(0.5, 0.5, 0.5))
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
return blockOb
def createInsetMCBlock(mcname, colourtriple, mcfaceindices, insets=[0,0,0], cyclesParams=None):
"""With no insets (the default), creates a full-size cube.
Else uses [bottom,top,sides] to inset the cube size and UV coords.
Side insets are applied symmetrically around the cube; maximum side inset is 7.
Units are in Minecraft texels - so from 1 to 15. Inset 16 is an error."""
blockname = mcname + 'Block'
if blockname in bpy.data.objects:
return bpy.data.objects[blockname]
pxlUnit = getUVUnit()
bpy.ops.object.mode_set(mode='OBJECT') #just to be sure... needed?
blockOb = createCubeMesh()
blockOb.name = blockname
mesh = blockOb.data
meshname = blockname + 'Mesh'
mesh.name = meshname
#Inset the mesh
verts = mesh.vertices
if isBMesh(): #inset the mesh, bmesh-version.
#loop the verts per face, change their .co by the inset amount.
#tverts = mesh.tessfaces.data.vertices # unneeded..
#polygon face order is: [left,back,right,front,bottom,top]
leface = mesh.polygons[0]
bkface = mesh.polygons[1]
rgface = mesh.polygons[2]
frface = mesh.polygons[3]
botface= mesh.polygons[4]
topface= mesh.polygons[5]
else:
botface = mesh.faces[0]
topface = mesh.faces[1]
rgface = mesh.faces[2]
frface = mesh.faces[3]
leface = mesh.faces[4]
bkface = mesh.faces[5]
bi = insets[0] * pxlUnit
ti = insets[1] * pxlUnit
si = insets[2] * pxlUnit
#does this need to be enforced as global rather than local coords?
#There are ways to inset these along their normal directions,
#but it's complex to understand, so I'll just inset all sides. :(
for v in topface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0], vp[1], vp[2]-ti))
for v in botface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0], vp[1], vp[2]+bi))
for v in rgface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0]-si, vp[1], vp[2]))
for v in frface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0], vp[1]+si, vp[2]))
for v in leface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0]+si, vp[1], vp[2]))
for v in bkface.vertices:
vtx = verts[v]
vp = vtx.co
vtx.co = Vector((vp[0], vp[1]-si, vp[2]))
#Fetch/setup the material.
blockMat = getMCMat(mcname, colourtriple, cyclesParams)
mcTexture = getMCTex()
blockMat.texture_slots.add() #it has 18, but unassignable...
mTex = blockMat.texture_slots[0]
mTex.texture = mcTexture
#set as active texture slot?
mTex.texture_coords = 'UV'
mTex.use_map_alpha = True #mibbe not needed?
mcuvs = None
if isBMesh():
mcuvs = createBMeshInsetUVs(mcname, mesh, blockMat, mcfaceindices, insets)
else:
mcuvs = createInsetUVs(mcname, mesh, blockMat, mcfaceindices, insets)
if mcuvs is not None:
mTex.uv_layer = mcuvs
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.rotate(value=(-1.5708), axis=(0, 0, 1), constraint_axis=(False, False, True), constraint_orientation='GLOBAL')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
#last, setup cycles on the material if user asked for it.
if cyclesParams is not None:
setupCyclesMat(blockMat, cyclesParams)
return blockOb
def createMCBlock(mcname, colourtriple, mcfaceindices, cyclesParams=None):
"""Creates a new minecraft WHOLE-block if it doesn't already exist, properly textured.
Array order for mcfaceindices is: [bottom, top, right, front, left, back]"""
#Has an instance of this blocktype already been made?
blockname = mcname + 'Block'
if blockname in bpy.data.objects:
return bpy.data.objects[blockname]
blockOb = createCubeMesh()
blockOb.name = blockname
blockMesh = blockOb.data
meshname = blockname + 'Mesh'
blockMesh.name = meshname
#Fetch/setup the material.
blockMat = getMCMat(mcname, colourtriple, cyclesParams)
# #ADD THE MATERIAL! (conditional on it already being applied?)
# blockMesh.materials.append(blockMat) # previously is in the uvtex creation function for some reason...
mcTexture = getMCTex()
blockMat.texture_slots.add() #it has 18, but unassignable...
mTex = blockMat.texture_slots[0]
mTex.texture = mcTexture
#set as active texture slot?
mTex.texture_coords = 'UV'
mTex.use_map_alpha = True #mibbe not needed?
mcuvs = None
if isBMesh():
mcuvs = createBMeshBlockCubeUVs(mcname, blockMesh, blockMat, mcfaceindices)
else:
mcuvs = createBlockCubeUVs(mcname, blockMesh, blockMat, mcfaceindices)
if mcuvs is not None:
mTex.uv_layer = mcuvs
#array order is: [bottom, top, right, front, left, back]
#for the cube's faces to align correctly to Minecraft north, based on the UV assignments I've bodged, correct it all by spinning the verts after the fact. :p
# -90degrees in Z. (clockwise a quarter turn)
# Or, I could go through a crapload more UV assignment stuff, which is no fun at all.
#bpy ENSURE MEDIAN rotation point, not 3d cursor pos.
bpy.ops.object.mode_set(mode='EDIT')
#bpy.ops.objects.editmode_toggle()
bpy.ops.mesh.select_all(action='SELECT')
#don't want toggle! Want "ON"!
bpy.ops.transform.rotate(value=(-1.5708), axis=(0, 0, 1), constraint_axis=(False, False, True), constraint_orientation='GLOBAL')
#bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
#last, setup cycles on the material if user asked for it.
if cyclesParams is not None:
setupCyclesMat(blockMat, cyclesParams)
return blockOb
def createFenceBlock(mcname, colourtriple, mcfaceindices, shapeParams, cyclesParams=None):
#create a central upright fencepost; determine side attachments during load process. ...
#mcname + "fencePost"
block = createInsetMCBlock(mcname, colourtriple, mcfaceindices, [0,0,6], cyclesParams)
print("Fence added. Shape params: %s" % shapeParams.__repr__)
return block
def createXBlock(basename, diffuseRGB, mcfaceindices, extraData, cycParams):
"""Creates an x-shaped billboard block if it doesn't already exist,
properly textured. Array order for mcfaceindices is: [\, /].
A single item facelist will be applied to both faces of the X."""
#Has one of this blocktype already been made?
blockname = basename + 'Block'
if blockname in bpy.data.objects:
return bpy.data.objects[blockname]
if not isBMesh():
return createMCBlock(basename, diffuseRGB, mcfaceindices, cycParams)
import bmesh
#BMesh-create X
m = bmesh.new()
xverts = [ (-0.45,0.45,0.5), #v1
(0.45,-0.45,0.5),
(0.45,-0.45,-0.5),
(-0.45,0.45,-0.5), #v4
(0.45,0.45,0.5), #v5
(-0.45,-0.45,0.5),
(-0.45,-0.45,-0.5),
(0.45,0.45,-0.5) #v8
]
for v in xverts:
m.verts.new(v)
#Looks like you can slice bm.verts! Nice!
f1 = m.faces.new(m.verts[0:4])
f2 = m.faces.new(m.verts[4:])
meshname = blockname + 'Mesh'
crossMesh = bpy.data.meshes.new(meshname)
m.to_mesh(crossMesh)
crossOb = bpy.data.objects.new(blockname, crossMesh)
#link it in! Unlike the primitive cube, it doesn't self-link.
bpy.context.scene.objects.link(crossOb)
#Fetch/setup the material.
crossMat = getMCMat(basename, diffuseRGB, cycParams)
mcTexture = getMCTex()
crossMat.texture_slots.add() #it has 18, but unassignable.
mTex = crossMat.texture_slots[0]
mTex.texture = mcTexture
#set as active texture slot?
mTex.texture_coords = 'UV'
mTex.use_map_alpha = True
mcuvs = None
mcuvs = createBMeshXBlockUVs(basename, crossMesh, crossMat, mcfaceindices)
if mcuvs is not None:
mTex.uv_layer = mcuvs
#last, setup cycles on the material if user asked for it.
if cycParams is not None:
setupCyclesMat(crossMat, cycParams)
return crossOb
def createBMeshXBlockUVs(blockname, me, matrl, faceIndices): #assume me is an X mesh. Returns name of the uv layer created.
"""Uses faceIndices, a list of per-face MC texture indices, to unwrap
the X's faces onto their correct places on terrain.png.
Face order for faceIndices is [\,/]"""
if faceIndices is None:
print("Warning: no face texture for %s" % blockname)
return
__listtype = type([])
if type(faceIndices) != __listtype:
if (type(faceIndices) == type(0)):
faceIndices = [faceIndices]*6
print("Applying singular value to all 6 faces")
else:
print("setting material and uvs for %s: non-numerical face list" % blockname)
print(faceIndices)
raise IndexError("improper face assignment data!")
if matrl.name not in me.materials:
me.materials.append(matrl)
uname = blockname + 'UVs'
if uname in me.uv_textures:
blockUVLayer = me.uv_textures[uname]
else:
blockUVLayer = me.uv_textures.new(name=uname)
#blockUVLoop = me.uv_loop_layers[-1] #works prior to 2.63?!
blockUVLoop = me.uv_layers.active
uvData = blockUVLoop.data
#face indices: our X mesh is put together in the right order, so
#should be just face 0, face 1 in the loop.
if len(faceIndices) == 1:
fOnly = faceIndices[0]
faceIndices = [fOnly, fOnly] #probably totally unecessary safety.
bmfi = [faceIndices[0], faceIndices[1]]
uvUnit = getUVUnit()
#offsets from topleft of any uv 'tile' to its vert corners (CCW from TR):
uvcorners = [(uvUnit, 0.0), (0.0,0.0), (0.0, -uvUnit), (uvUnit,-uvUnit)]
#we assign each UV in sequence of the 'loop' for the whole mesh: 8 for an X
xim = getMCImg()
meshtexfaces = blockUVLayer.data.values()
matrl.game_settings.alpha_blend = 'CLIP'
matrl.game_settings.use_backface_culling = False
#faceNo = 0 #or enumerate me.polygons?
#face order is: [\,/]
for faceNo, pface in enumerate(me.polygons):
face = meshtexfaces[faceNo]
face.image = xim
faceTexId = bmfi[faceNo]
#calculate the face location on the uvmap
mcTexU = getTextureAtlasU(faceTexId)
mcTexV = getTextureAtlasV(faceTexId)
#multiply by square size to get U1,V1 (topleft):
u1 = (mcTexU * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # or >> 4 (div by imagesize to get as fraction)
v1 = (mcTexV * TEXTURE_ATLAS_PIXELS_PER_UNIT) / TEXTURE_ATLAS_PIXELS # ..
v1 = 1.0 - v1 #y goes low to high #DEBUG print("That means u1,v1 is %f,%f" % (u1,v1))
#DEBUG
if DEBUG_BBUV:
print("createBMeshXBlockUVs %s u1,v1 %f,%f" % (blockname,u1,v1))
loopPolyStart = pface.loop_start #where its verts start in loop. :D
#if loop total's not 4, need to work with ngons/tris or do more complex stuff.
loopPolyCount = pface.loop_total
loopPolyEnd = loopPolyStart + loopPolyCount
corners = uvcorners
for n, loopV in enumerate(range(loopPolyStart, loopPolyEnd)):
offset = corners[n] # 0..3
mcUV = Vector((u1+offset[0], v1+offset[1]))
uvData[loopV].uv = mcUV
#faceNo += 1
#a guess. does this actually help? YES! Without it all the world's grey and textureless!
me.tessface_uv_textures.data.update()
#but then, sometimes it's grey anyway. :(
return "".join([blockname, 'UVs'])
def createStairsBlock(basename, diffuseRGB, mcfaceindices, extraData, cycParams):
"""Creates a stairs block if it doesn't already exist,
properly textured. Will create new stair blocks by material,
direction and inversion."""
#DOES THE FACING DETERMINE THE UV UNWRAP? The public needs to know! if so... nuts! must be easier way? Can do cube mapping and rotate tex space??
#Has one of this already been made?
#... get direction and bytes unpack verticality
blockname = basename + 'Block'
if blockname in bpy.data.objects:
return bpy.data.objects[blockname]
if not isBMesh():
return createMCBlock(basename, diffuseRGB, mcfaceindices, cycParams)
import bmesh
#BMesh-create X
stair = bmesh.new()
#Stair Vertices
sverts = [ (0.5,0.5,0.5), #v0
(0.5,0.5,-0.5), #v1
(0.5,-0.5,-0.5), #v2
(0.5,-0.5,0), #v3
(0.5,0,0), #v4
(0.5,0,0.5), #v5 -- X+ facing stair profile done.
(-0.5,0.5,0.5), #v6
(-0.5,0.5,-0.5), #v7
(-0.5,-0.5,-0.5), #v8
(-0.5,-0.5,0), #v9
(-0.5,0,0), #v10
(-0.5,0,0.5), #v11 -- X- facing stair profile done.
#would it be a good idea or a bad idea to reverse order of these latter 6?
]
for v in sverts:
stair.verts.new(v)
svs = stair.verts
#now the faces. in a specific order we can follow for unwrapping later
#in a stair mesh, we'll have R1,R2 ; stairfacings(vertical) higher,lower; L1,L2; BACK; Top(tip),Top(midstep); Bottom. Maybe. Rearrange for cube order.
sf1 = stair.faces.new([svs[0], svs[5], svs[4], svs[1]]) #r1
sf2 = stair.faces.new([svs[4], svs[3], svs[2], svs[1]]) #r2
sf3 = stair.faces.new([svs[5], svs[11], svs[10],svs[4]]) #vertical topstair face
sf4 = stair.faces.new([svs[3], svs[9], svs[8],svs[2]]) #vertical bottomstair face
sf5 = stair.faces.new([svs[9], svs[10], svs[7],svs[8]]) #lface1 (lower..)
sf6 = stair.faces.new([svs[11],svs[6],svs[7],svs[10]]) #lface2 (upright higher bit)
sf7 = stair.faces.new([svs[6], svs[0], svs[1],svs[7]]) #back
sf8 = stair.faces.new([svs[0], svs[6], svs[11],svs[5]]) #topface, topstep
sf9 = stair.faces.new([svs[4], svs[10], svs[9],svs[3]]) #topface, midstep
sf10= stair.faces.new([svs[7], svs[1], svs[2],svs[8]]) #bottom
#check the extra data for direction and upside-downness.
sm = bpy.data.meshes.new("StairMesh")
stob = bpy.data.objects.new("Stair", sm)
bpy.context.scene.objects.link(stob)
stair.to_mesh(sm)
#f1 = m.faces.new([v1,v2,v3,v4])
#loop1 = f1.loops[0]
#me = bpy.data.meshes.new("Foo")
#ob = bpy.data.objects.new("Bar", me)
#bpy.context.scene.objects.link(ob)
pass
# #################################################
#if __name__ == "__main__":
# #BlockBuilder.create ... might tidy up namespace.
# #nublock = createMCBlock("Glass", (1,2,3), [49]*6)
# #nublock2 = createInsetMCBlock("Torch", (240,150,50), [80]*6, [0,6,7])
# nublock3 = createInsetMCBlock("Chest", (164,114,39), [25,25,26,27,26,26], [0,1,1])