927 lines
43 KiB
Python
927 lines
43 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
|
|
#
|
|
#
|
|
#
|
|
# Acro's Python3.2 NBT Reader for Blender Importing Minecraft
|
|
|
|
#TODO Possible Key Options for the importer:
|
|
|
|
#TODO: load custom save locations, rather than default saves folder.
|
|
#good for backup/server game reading.
|
|
# what's a good way to swap out the world-choice dialogue for a custom path input??
|
|
|
|
#"Surface only": use the heightmap and only load surface.
|
|
#Load more than just the top level, obviously, cos of cliff
|
|
#walls, caves, etc. water should count as transparent for this process,
|
|
#as should glass, flowers, torches, portal; all nonsolid block types.
|
|
|
|
#"Load horizon" / "load radius": should be circular, or have options
|
|
|
|
#import bpy
|
|
#from bpy.props import FloatVectorProperty
|
|
#from mathutils import Vector
|
|
import numpy as npy
|
|
#import blockbuild
|
|
import sysutil
|
|
import blockconversion
|
|
#using blockbuild.createMCBlock(mcname, diffuseColour, mcfaceindices)
|
|
#faceindices order: (bottom, top, right, front, left, back)
|
|
#NB: this should probably change, as it was started by some uv errors.
|
|
|
|
import nbtreader
|
|
#level.dat, .mcr McRegion, .mca Anvil: all different formats, but all are NBT.
|
|
|
|
import sys, os, gzip
|
|
import datetime
|
|
from struct import calcsize, unpack, error as StructError
|
|
|
|
#tag classes: switch/override the read functions once they know what they are
|
|
#and interpret payload by making more taggy bits as needed inside self.
|
|
#maybe add mcpath as a context var so it can be accessed from operators.
|
|
|
|
REPORTING = {}
|
|
REPORTING['totalchunks'] = 0
|
|
totalchunks = 0
|
|
wseed = None #store chosen world's worldseed, handy for slimechunk calcs.
|
|
|
|
MCREGION_VERSION_ID = 0x4abc; # Check world's level.dat 'version' property for these.
|
|
ANVIL_VERSION_ID = 0x4abd; #
|
|
|
|
#TODO: Retrieve these from bpy.props properties stuck in the scene RNA.
|
|
EXCLUDED_BLOCKS = [1, 3, 87] #(1,3,87) # hack to reduce loading / slowdown: (1- Stone, 3- Dirt, 87 netherrack). Other usual suspects are Grass,Water, Leaves, Sand,StaticLava
|
|
|
|
LOAD_AROUND_3D_CURSOR = False #calculates 3D cursor as a Minecraft world position, and loads around that instead of player (or SMP world spawn) position
|
|
|
|
unknownBlockIDs = set()
|
|
|
|
OPTIONS = {}
|
|
|
|
#"Profile" execution checks for measuring whether optimisations are worth it:
|
|
|
|
REPORTING['blocksread'] = 0
|
|
REPORTING['blocksdropped'] = 0
|
|
t0 = datetime.datetime.now()
|
|
tReadAndBuffered = -1
|
|
tToMesh = -1
|
|
tChunk0 = -1 #these don't need to be globals - just store the difference in the arrays.
|
|
tChunkEnd = -1
|
|
tRegion0 = -1
|
|
tRegionEnd = -1
|
|
tChunkReadTimes = []
|
|
tRegionReadTimes = []
|
|
|
|
WORLD_ROOT = None
|
|
|
|
#MCBINPATH -- in /bin, zipfile open minecraft.jar, and get terrain.png.
|
|
#Feed directly into Blender, or save into the Blender temp dir, then import.
|
|
print("Mineblend saved games location: "+sysutil.getMCPath())
|
|
|
|
#Blockdata: [name, diffuse RGB triple, texture ID list, extra data? (XD/none),
|
|
# custom model shape (or None), shape params (or None if not custom mesh),
|
|
# and finally dictionary of Cycles params (see blockbuild.)
|
|
# TexID list is [bot, top, right, front, left back] or sometimes other orders/lengths if custom model
|
|
# Texture IDs are the 1d (2d) count of location of their 16x16 square within terrain.png in minecraft.jar
|
|
|
|
#Don't store a name for air. Ignore air.
|
|
# Order for Blender cube face creation is: [bottom, top, right, front, left, back]
|
|
|
|
BLOCKDATA = {0: ['Air'],
|
|
1: ['Stone', (116,116,116), [308]*6],
|
|
2: ['Grass', (95,159,53), [200,148,332,332,332,332]],
|
|
3: ['Dirt', (150, 108, 74), [200]*6],
|
|
4: ['Cobblestone', (94,94,94), [163]*6],
|
|
5: ['WoodenPlank', (159,132,77), [176]*6],
|
|
6: ['Sapling', (0,100,0), [20]*6, 'XD', 'cross'],
|
|
7: ['Bedrock', [51,51,51], [100]*6],
|
|
8: ['WaterFlo', (31,85,255), [2]*6, None, None, None, {'alpha': True}],
|
|
9: ['Water', (62,190,255), [2]*6, None, None, None, {'alpha': True}],
|
|
10: ['LavaFlo', (252,0,0), [0]*6, None, None, None, {'emit': 1.10, 'stencil': False}],
|
|
11: ['Lava', (230,0,0), [0]*6, None, None, None, {'emit': 1.10, 'stencil': False}],
|
|
12: ['Sand', (214,208,152), [243]*6],
|
|
13: ['Gravel', (154,135,135), [352]*6],
|
|
14: ['GoldOre', (252,238,75), [331]*6],
|
|
15: ['IronOre', (216,175,147), [395]*6],
|
|
16: ['CoalOre', (69,69,69), [161]*6],
|
|
17: ['Wood', (76,61,38), [452,452,451,451,451,451], 'XD'],
|
|
18: ['Leaves', (99,128,15), [425]*6, None, None, None, {'stencil': False, 'leaf': True}], #TODO: XD colour+texture.
|
|
19: ['Sponge', (206,206,70), [244]*6], # FIXME - wet sponge
|
|
20: ['Glass', (254,254,254), [263]*6, None, None, None, {'stencil': True}],
|
|
21: ['LapisLazuliOre', (28,87,198), [418]*6],
|
|
22: ['LapisLazuliBlock', (25,90,205), [417]*6],
|
|
23: ['Dispenser', (42,42,42), [262,262,261,41,261,261]], # TODO - front?
|
|
24: ['Sandstone', (215,209,153), [307,307,339,339,339,339], 'XD'], #!!
|
|
25: ['NoteBlock', (145,88,64), [398]*6], #python sound feature? @see dr epilepsy.
|
|
26: ['Bed'], #inset, directional. xd: if head/foot + dirs.
|
|
27: ['PwrRail', (204,93,22), [433]*6, 'XD', 'onehigh', None, {'stencil': True}], #meshtype-> "rail". define as 1/16thHeightBlock, read extra data to find orientation.
|
|
28: ['DetRail', (134,101,100), [465]*6, 'XD', 'onehigh', None, {'stencil': True}], #change meshtype to "rail" for purposes of slanted bits. later. PLANAR, too. no bottom face.
|
|
29: ['StickyPiston', (114,120,70), [109,491,493,493,493,493], 'XD', 'pstn'],
|
|
30: ['Cobweb', (237,237,237), [54]*6, 'none', 'cross', None, {'stencil': True}],
|
|
# tried 370, 434
|
|
31: ['TallGrass', (52,79,45), [213,213,213,213,213,213], 'XD', 'cross', None, {'stencil': True}],
|
|
32: ['DeadBush', (148,100,40), [225]*6, None, 'cross', None, {'stencil': True}],
|
|
33: ['Piston', (114,120,70), [491,494,493,493,493,493], 'XD', 'pstn'],
|
|
34: ['PistonHead', (188,152,98), [494]*6], #or top is 106 if sticky (extra data)
|
|
35: ['Wool', (235,235,235), [279]*6, 'XD'], #XD means use xtra data...
|
|
37: ['Dandelion', (204,211,2), [79]*6, 'no', 'cross', None, {'stencil': True}],
|
|
38: ['Rose', (247,7,15), [207]*6, 'no', 'cross', None, {'stencil': True}],
|
|
39: ['BrownMushrm', (204,153,120), [480]*6, 'no', 'cross', None, {'stencil': True}],
|
|
40: ['RedMushrm', (226,18,18), [481]*6, 'no', 'cross', None, {'stencil': True}],
|
|
41: ['GoldBlock', (255,241,68), [330]*6], # Todo: metalic
|
|
42: ['IronBlock', (230,230,230), [394]*6],
|
|
43: ['DblSlabs', (255,255,0), [53,53,21,21,21,21], 'XD', 'twoslab'], #xd for type
|
|
44: ['Slabs', (255,255,0), [53,53,21,21,21,21], 'XD', 'slab'], #xd for type
|
|
45: ['BrickBlock', (124,69,24), [101]*6],
|
|
46: ['TNT', (219,68,26), [245,309,277,277,277,277]],
|
|
47: ['Bookshelf', (180,144,90), [144,144,5,5,5,5]],
|
|
48: ['MossStone', (61,138,61), [164]*6],
|
|
49: ['Obsidian', (60,48,86), [141]*6],
|
|
50: ['Torch', (240,150,50), [426]*6, 'XD', 'inset', [0,6,7], {'stencil': True}],
|
|
51: ['Fire', (255,100,100), [56]*6, None, 'hash', None, {'emit': 1.0, 'stencil': True}], #TODO: Needed for Nether. maybe use hash mesh '#'
|
|
52: ['MonsterSpawner', (27,84,124), [65]*6, None, None, None, {'stencil': True}], #xtra data for what's spinning inside it??
|
|
53: ['WoodenStairs', (159,132,77), [4,4,4,4,4,4], 'XD', 'stairs'], # TODO
|
|
54: ['Chest', (164,114,39), [25,25,26,27,26,26], 'XD', 'chest'], #texface ordering is wrong # TODO
|
|
55: ['RedStnWire', (255,0,3), [434]*6, 'XD', 'onehigh', None, {'stencil': True}], #FSM-dependent, may need XD. Also, texture needs to act as bitmask alpha only, onto material colour on this thing. # TODO alpha color
|
|
56: ['DiamondOre', (93,236,245), [168]*6],
|
|
57: ['DiamondBlock', (93,236,245), [136]*6],
|
|
58: ['CraftingTbl', (160,105,60), [197,197,196,195,196,195]],
|
|
59: ['Seeds', (160,184,0), [310]*6, 'XD', 'crops', None, {'stencil': True}],
|
|
60: ['Farmland', (69,41,21), [200,110,200,200,200,200]],
|
|
61: ['Furnace', (42,42,42), [262,262,261,259,261,261]], #[bottom, top, right, front, left, back]
|
|
62: ['Burnace', (50,42,42), [262,262,261,259,261,261]],
|
|
63: ['SignPost', (159,132,77), [579]*6, 'XD', 'sign'],
|
|
64: ['WoodDoor', (145,109,56), [193,193,283,283,283,283], 'XD', 'door', None, {'stencil': True}], # FIXME top/bot
|
|
65: ['Ladder', (142,115,60), [416]*6, None, None, None, {'stencil': True}],
|
|
66: ['Rail', (172,136,82), [82]*6, 'XD', 'onehigh', None, {'stencil': True}], #to be refined for direction etc.
|
|
67: ['CobbleStairs', (77,77,77), [163]*6, 'XD', 'stairs'],
|
|
68: ['WallSign', (159,132,77), [579]*6, 'XD', 'wallsign'], #TODO: UVs! + Model!
|
|
69: ['Lever', (105,84,51), [426]*6, 'XD', 'lever'],
|
|
70: ['StnPressPlate', (110,110,110), [372]*6, 'no', 'onehigh'],
|
|
71: ['IronDoor', (183,183,183), [187,187,187,187,187,187], 'XD', 'door', None, {'stencil': True}], # TODO top/bot
|
|
72: ['WdnPressPlate', (159,132,77), [4]*6, 'none', 'onehigh'], #TODO
|
|
73: ['RedstOre', (151,3,3), [51]*6],
|
|
74: ['RedstOreGlowing', (255,3,3), [51]*6], #wth!
|
|
75: ['RedstTorchOff', (86,0,0), [83]*6, 'XD', 'inset', [0,6,7]], #TODO Proper RStorch mesh
|
|
76: ['RedstTorchOn', (253,0,0), [115]*6, 'XD', 'inset', [0,6,7]], #todo: 'rstorch'
|
|
77: ['StoneButton', (116,116,116), [1]*6, 'btn'], # TODO
|
|
78: ['Snow', (240,240,240), [180]*6, 'XD', 'onehigh'], #snow has height variants 0-7. 7 is full height block. Curses!
|
|
79: ['Ice', (220,220,255), [391]*6],
|
|
80: ['SnowBlock', (240,240,240), [180]*6], #xd determines height.
|
|
81: ['Cactus', (20,141,36), [70,70,38,38,38,38], 'none', 'cactus'],
|
|
82: ['ClayBlock', (170,174,190), [135]*6],
|
|
83: ['SugarCane', (130,168,89), [147]*6, None, 'cross', None, {'stencil': True}],
|
|
84: ['Jukebox', (145,88,64), [489,399,489,489,489,489]], #XD
|
|
85: ['Fence', (160,130,70), [4]*6, 'none', 'fence'], #fence mesh, extra data. #TODO
|
|
86: ['Pumpkin', (227,144,29), [113,113,17,464,17,17]],
|
|
87: ['Netherrack', (137,15,15), [488]*6],
|
|
88: ['SoulSand', (133,109,94), [212]*6],
|
|
89: ['Glowstone', (114,111,73), [329]*6, None, None, None, {'emit': 0.95, 'stencil': False}], #cycles: emitter!
|
|
90: ['Portal', (150,90,180), None], # TODO - shouldn't this be [208]*6?
|
|
91: ['JackOLantern',(227,144,29), [113,113,17,496,17,17], 'XD'], #needs its facing dir.
|
|
92: ['Cake', (184,93,39), [124,71,39,39,39,39], 'XD', 'inset', [0,8,1]], # TODO - bot
|
|
93: ['RedRepOff', (176,176,176), [179]*6, 'xdcircuit', 'onehigh'], #TODO 'redrep' meshtype
|
|
94: ['RedRepOn', (176,176,176), [211]*6, 'xdcircuit', 'onehigh'], #TODO 'redrep' meshtype
|
|
#95: ['LockedChest', (164,114,39), [25,25,26,27,26,26], 'xd', 'chest'], #texface order wrong (see #54)
|
|
# When stencil set, blocks are non-textured and opaque... unanticipated state?
|
|
95: ['StainedGlass', (164,114,39), [327]*6, 'XD', None, None, {'alpha': True}], #texface order wrong (see #54)
|
|
96: ['Trapdoor', (117,70,34), [373]*6, 'XD', 'inset', [0,13,0]],
|
|
97: ['HiddenSfish', (116,116,116), [335]*6],
|
|
98: ['StoneBricks', (100,100,100), [85]*6, 'XD'],
|
|
99: ['HgRedM', (210,177,125), [462]*6, 'XD'], #XD for part/variant/colour (stalk/main)
|
|
100: ['HgBrwM', (210,177,125), [461]*6, 'XD'],
|
|
101: ['IronBars', (171,171,173), [393]*6, 'XD', 'pane'],
|
|
102: ['GlassPane', (254,254,254), [263]*6, 'XD', 'pane', None, {'stencil': True}],
|
|
103: ['Melon', (166,166,39), [458,458,455,455,455,455]],
|
|
104: ['PumpkinStem'], # TODO 457?
|
|
105: ['MelonStem'], # TODO 457?
|
|
106: ['Vines', (39,98,13), [469]*6, 'XD', 'wallface'],
|
|
107: ['FenceGate', (143,115,73), [4]*6], #TODO
|
|
108: ['BrickStairs', (135,74,58), [101]*6, 'XD', 'stairs'], #TODO
|
|
109: ['StoneBrickStairs', (100,100,100), [85]*6, 'XD', 'stairs'], #TODO
|
|
110: ['Mycelium', (122,103,108), [200,483,482,482,482,482]], #useful to ignore option? as this is Dirt top in Mushroom Biomes.
|
|
111: ['LilyPad', (12,94,19), [22]*6, 'none', 'onehigh', None, {'stencil': True}],
|
|
112: ['NethrBrick', (48,24,28), [484]*6],
|
|
113: ['NethrBrickFence', (48,24,28), [484]*6, 'none', 'fence'],
|
|
114: ['NethrBrickStairs', (48,24,28), [484]*6, 'XD', 'stairs'],
|
|
115: ['NethrWart', (154,39,52), [487]*6],
|
|
116: ['EnchantTab', (116,30,29), [141,205,173,173,173,173], 'none', 'inset', [0,4,0]], #TODO enchantable with book?
|
|
117: ['BrewStnd', (207,227,186), [157]*6, 'x', 'brewstand'], #fully custom model # TODO
|
|
118: ['Cauldron', (55,55,55), [139,138,154,154,154,154]], #fully custom model # TODO
|
|
119: ['EndPortal', (0,0,0), None], #TODO
|
|
120: ['EndPortalFrame', (144,151,110), [237,175,78,46,46,46,46]],
|
|
121: ['EndStone', (144,151,110), [237]*6],
|
|
122: ['DragonEgg', (0,0,0)], #TODO
|
|
123: ['RedstLampOff', (140,80,44), [498]*6],
|
|
124: ['RedstLampOn', (247,201,138), [19]*6, None, None, None, {'emit': 0.95, 'stencil': False}],
|
|
129: ['EmeraldOre', (140,80,44), [109]*6],
|
|
133: ['EmeraldBlock', (140,80,44), [77]*6],
|
|
138: ['Beacon', (247,201,138), [96]*6, None, None, None, {'emit': 1.2, 'stencil': False}], # TODO - encased in glass
|
|
152: ['Redstone', (247,201,138), [337]*6],
|
|
153: ['NetherQuartzOre', (247,201,138), [369]*6],
|
|
155: ['Quartz', (247,201,138), [145]*6], # TODO - variants
|
|
159: ['StainedClay', (247,201,138), [384]*6, 'XD'],
|
|
162: ['Acacia', (247,201,138), [428,428,427,427,427,427]], # TODO - dark oak
|
|
168: ['Prismarine', (127, 255, 212), [432]*6, 'XD'],
|
|
169: ['SeaLantern', (247,201,138), [116]*6, None, None, None, {'emit': 1.2, 'stencil': False}], # TODO - encased in glass
|
|
170: ['HayBale', (247,201,138), [387,387,386,386,386,386]],
|
|
172: ['HardenedClay', (247,201,138), [353]*6],
|
|
173: ['BlockOfCoal', (247,201,138), [160]*6],
|
|
174: ['PackedIce', (247,201,138), [392]*6],
|
|
179: ['RedSandstone', (247,201,138), [306,306,242,242,242,242]*6]
|
|
}
|
|
#And anything new Mojang add in with each update!
|
|
|
|
BLOCKVARIANTS = {
|
|
#Saplings: normal, spruce, birch and jungle types
|
|
6: [ [''],
|
|
['Spruce', (57,90,57), [63]*6],
|
|
['Birch', (207,227,186), [79]*6],
|
|
['Jungle', (57,61,13), [30]*6]
|
|
],
|
|
|
|
17: [ [''],#normal wood (oak)
|
|
['Spruce',(76,61,38), [454,454,453,453,453,453]],
|
|
['Birch', (76,61,38), [448,448,431,431,431,431]],
|
|
['Jungle',(89,70,27), [450,450,449,449,449,449]],
|
|
],
|
|
#TODO: adjust leaf types, too!
|
|
|
|
24: [ [''],#normal 'cracked' sandstone
|
|
['Decor', (215,209,153), [403,403,301,301,301,301]],
|
|
['Smooth',(215,209,153), [403,403,371,371,371,371]],
|
|
],
|
|
|
|
# Tallgrass - TODO
|
|
#31: [ [''],
|
|
# ['', (,,), []*6],
|
|
# ['', (,,), []*6],
|
|
# ],
|
|
# Wool
|
|
35: [ [''],
|
|
['Orange', (255,150,54), [119]*6], #custom tex coords!
|
|
['Magenta', (227,74,240), [87]*6],
|
|
['LightBlue', (83,146,255), [23]*6],
|
|
['Yellow', (225,208,31), [311]*6],
|
|
['LightGreen', (67,218,53), [55]*6],
|
|
['Pink', (248,153,178), [151]*6],
|
|
['Grey', (75,75,75), [470]*6],
|
|
['LightGrey', (181,189,189), [247]*6],
|
|
['Cyan', (45,134,172), [438]*6],
|
|
['Purple', (134,53,204), [183]*6],
|
|
['Blue', (44,58,176), [374]*6],
|
|
['Brown', (99,59,32), [406]*6],
|
|
['DarkGreen', (64,89,27), [502]*6],
|
|
['Red', (188,51,46), [215]*6],
|
|
['Black', (28,23,23), [342]*6]
|
|
],
|
|
#doubleslabs
|
|
#38: [ [''],] # TODO - flowers
|
|
|
|
43: [ [''], #stone slabs (default)
|
|
['SndStn', (215,209,153), [339]*6],
|
|
['Wdn', (159,132,77), [176]*6],
|
|
['Cobl', (94,94,94), [163]*6],
|
|
['Brick', (124,69,24), [101]*6],
|
|
['StnBrk', (100,100,100), [85]*6],
|
|
[''],
|
|
],
|
|
|
|
#slabs
|
|
44: [ [''], #stone slabs (default)
|
|
['SndStn', (215,209,153), [192]*6],
|
|
['Wdn', (159,132,77), [4]*6],
|
|
['Cobl', (94,94,94), [16]*6],
|
|
['Brick', (124,69,24), [7]*6],
|
|
['StnBrk', (100,100,100), [54]*6],
|
|
[''],
|
|
],
|
|
|
|
50: [ [''], #nowt on 0...
|
|
['Ea'], #None for colour, none Tex, then: CUSTOM MESH
|
|
['We'],
|
|
['So'],
|
|
['Nr'],
|
|
['Up']
|
|
],
|
|
|
|
59: [ ['0', (160,184,0), [88]*6], #?
|
|
['1', (160,184,0), [89]*6],
|
|
['2', (160,184,0), [90]*6],
|
|
['3', (160,184,0), [91]*6],
|
|
['4', (160,184,0), [92]*6],
|
|
['5', (160,184,0), [93]*6],
|
|
['6', (160,184,0), [94]*6],
|
|
['7', (160,184,0), [95]*6],
|
|
],
|
|
# stained glass
|
|
95: [ ['White', (255,255,255), [327]*6],
|
|
['Orange', (255,150,54), [289]*6], #custom tex coords!
|
|
['Magenta', (227,74,240), [288]*6],
|
|
['LightBlue', (83,146,255), [267]*6],
|
|
['Yellow', (225,208,31), [328]*6],
|
|
['LightGreen', (67,218,53), [271]*6],
|
|
['Pink', (248,153,178), [323]*6],
|
|
['Grey', (75,75,75), [268]*6],
|
|
['LightGrey', (181,189,189), [326]*6],
|
|
['Cyan', (45,134,172), [270]*6],
|
|
['Purple', (134,53,204), [324]*6],
|
|
['Blue', (44,58,176), [265]*6],
|
|
['Brown', (99,59,32), [266]*6],
|
|
['DarkGreen', (64,89,27), [269]*6],
|
|
['Red', (188,51,46), [325]*6],
|
|
['Black', (28,23,23), [264]*6]
|
|
],
|
|
|
|
#stone brick moss/crack/circle variants:
|
|
98: [ [''],
|
|
['Mossy', (100,100,100), [181]*6],
|
|
['Cracked',(100,100,100), [149]*6],
|
|
['Circle', (100,100,100), [117]*6],
|
|
],
|
|
#hugebrownmush:
|
|
99: [ [''], #default (pores on all sides)
|
|
['CrTWN',(210,177,125),[142,126,142,142,126,126]],#1
|
|
['SdTN',(210,177,125),[142,126,142,142,142,126]],#2
|
|
['CrTEN',(210,177,125),[142,126,126,142,142,126]],#3
|
|
['SdTW',(210,177,125),[142,126,142,142,126,142]],#4
|
|
['Top',(210,177,125),[142,126,142,142,142,142]],#5
|
|
['SdTE',(210,177,125),[142,126,126,142,142,142]],#6
|
|
['CrTSW',(210,177,125),[142,126,142,126,126,142]],#7
|
|
['SdTS',(210,177,125),[142,126,142,126,142,142]],#8
|
|
['CrTES',(210,177,125),[142,126,126,126,142,142]],#9
|
|
['Stem',(215,211,200),[142,142,141,141,141,141]]#10
|
|
],
|
|
#hugeredmush:
|
|
100:[ [''], #default (pores on all sides)
|
|
['CrTWN',(188,36,34),[142,125,142,142,125,125]],#1
|
|
['SdTN',(188,36,34),[142,125,142,142,142,125]],#2
|
|
['CrTEN',(188,36,34),[142,125,125,142,142,125]],#3
|
|
['SdTW',(188,36,34),[142,125,142,142,125,142]],#4
|
|
['Top',(188,36,34),[142,125,142,142,142,142]],#5
|
|
['SdTE',(188,36,34),[142,125,125,142,142,142]],#6
|
|
['CrTSW',(188,36,34),[142,125,142,125,125,142]],#7
|
|
['SdTS',(188,36,34),[142,125,142,125,142,142]],#8
|
|
['CrTES',(188,36,34),[142,125,125,125,142,142]],#9
|
|
['Stem',(215,211,200),[142,142,141,141,141,141]]#10
|
|
],
|
|
|
|
# stained clay
|
|
159: [[''], # ['White', (255,255,255), [384]*6],
|
|
['Orange', (255,150,54), [363]*6], #custom tex coords!
|
|
['Magenta', (227,74,240), [362]*6],
|
|
['LightBlue', (83,146,255), [355]*6],
|
|
['Yellow', (225,208,31), [385]*6],
|
|
['LightGreen', (67,218,53), [361]*6],
|
|
['Pink', (248,153,178), [364]*6],
|
|
['Grey', (75,75,75), [358]*6],
|
|
['LightGrey', (181,189,189), [367]*6],
|
|
['Cyan', (45,134,172), [357]*6],
|
|
['Purple', (134,53,204), [365]*6],
|
|
['Blue', (44,58,176), [354]*6],
|
|
['Brown', (99,59,32), [356]*6],
|
|
['DarkGreen', (64,89,27), [359]*6],
|
|
['Red', (188,51,46), [366]*6],
|
|
['Black', (28,23,23), [354]*6]
|
|
],
|
|
|
|
# prismarine
|
|
168: [[''],
|
|
['Bricks', (127, 255, 212), [368]*6],
|
|
['Dark', (127, 255, 212), [400]*6],
|
|
]
|
|
}
|
|
|
|
def readLevelDat():
|
|
"""Reads the level.dat for info like the world name, player inventory..."""
|
|
lvlfile = gzip.open('level.dat', 'rb')
|
|
|
|
#first byte must be a 10 (TAG_Compound) containing all else.
|
|
#read a TAG_Compound...
|
|
#rootTag = Tag(lvlfile)
|
|
|
|
rootTag = nbtreader.TagReader.readNamedTag(lvlfile)[1] #don't care about the name... or do we? Argh, it's a named tag but we throw the blank name away.
|
|
|
|
print(rootTag.printTree(0)) #give it repr with an indent param...?
|
|
|
|
|
|
def readRegion(fname, vertexBuffer):
|
|
#A region has an 8-KILObyte header, of 1024 locations and 1024 timestamps.
|
|
#Then from 8196 onwards, it's chunk data and (arbitrary?) gaps.
|
|
#Chunks are zlib compressed & have their own structure, more on that later.
|
|
print('== Reading region %s ==' % fname)
|
|
|
|
rfile = open(fname, 'rb')
|
|
regionheader = rfile.read(8192)
|
|
|
|
chunklist = []
|
|
chunkcount = 0
|
|
cio = 0 #chunk index offset
|
|
while cio+4 <= 4096: #only up to end of the locations! (After that is timestamps)
|
|
cheadr = regionheader[cio:cio+4]
|
|
# 3 bytes "offset" -- how many 4kiB disk sectors away the chunk data is from the start of the file.
|
|
# 1 byte "sector count" -- how many 4kiB disk sectors long the chunk data is.
|
|
#(sector count is rounded up during save, so gives the last disk sector in which there's data for this chunk)
|
|
|
|
offset = unpack(">i", b'\x00'+cheadr[0:3])[0]
|
|
chunksectorcount = cheadr[3] #last of the 4 bytes is the size (in 4k sectors) of the chunk
|
|
|
|
chunksLoaded = 0
|
|
if offset != 0 and chunksectorcount != 0: #chunks not generated as those coordinates yet will be blank!
|
|
chunkdata = readChunk(rfile, offset, chunksectorcount) #TODO Make sure you seek back to where you were to start with ...
|
|
chunksLoaded += 1
|
|
chunkcount += 1
|
|
|
|
chunklist.append((offset,chunksectorcount))
|
|
|
|
cio += 4
|
|
|
|
rfile.close()
|
|
|
|
print("Region file %s contains %d chunks." % (fname, chunkcount))
|
|
return chunkcount
|
|
|
|
def toChunkPos(pX,pZ):
|
|
return (pX/16, pZ/16)
|
|
|
|
# def batchBuild(meshBuffer):
|
|
# #build all geom from pydata as meshes in one shot. :) This is fast.
|
|
# for meshname in (meshBuffer.keys()):
|
|
# me = bpy.data.meshes[meshname]
|
|
# me.from_pydata(meshBuffer[meshname], [], [])
|
|
# me.update()
|
|
|
|
|
|
# def mcToBlendCoord(chunkPos, blockPos):
|
|
# """Converts a Minecraft chunk X,Z pair and a Minecraft ordered X,Y,Z block
|
|
# location triple into a Blender coordinate vector Vx,Vy,Vz.
|
|
# Just remember: in Minecraft, Y points to the sky."""
|
|
#
|
|
# # Mapping Minecraft coords -> Blender coords
|
|
# # In Minecraft, +Z (west) <--- 0 ----> -Z (east), while North is -X and South is +X
|
|
# # In Blender, north is +Y, south is-Y, west is -X and east is +X.
|
|
# # So negate Z and map it as X, and negate X and map it as Y. It's slightly odd!
|
|
#
|
|
# vx = -(chunkPos[1] << 4) - blockPos[2]
|
|
# vy = -(chunkPos[0] << 4) - blockPos[0] # -x of chunkpos and -x of blockPos (x,y,z)
|
|
# vz = blockPos[1] #Minecraft's Y.
|
|
#
|
|
# return Vector((vx,vy,vz))
|
|
|
|
|
|
# def getMCBlockType(blockID, extraBits):
|
|
# """Gets reference to a block type mesh, or creates it if it doesn't exist.
|
|
# The mesh created depends on meshType from the global blockdata (whether it's torch or repeater, not a cube)
|
|
# These also have to be unique and differently named for directional versions of the same thing - eg track round a corner or up a slope.
|
|
# This also ensures material and name are set."""
|
|
# import blockbuild
|
|
# global OPTIONS #, BLOCKDATA (surely!?)
|
|
#
|
|
# bdat = BLOCKDATA[blockID]
|
|
#
|
|
# corename = bdat[0] # eg mcStone, mcTorch
|
|
#
|
|
# if len(bdat) > 1:
|
|
# colourtriple = bdat[1]
|
|
# else:
|
|
# colourtriple = [214,127,255] #shocking pink
|
|
#
|
|
# mcfaceindices = None #[]
|
|
# if len(bdat) > 2 and bdat[2] is not None:
|
|
# mcfaceindices = bdat[2]
|
|
#
|
|
# usesExtraBits = False
|
|
# if len(bdat) > 3:
|
|
# usesExtraBits = (bdat[3] == 'XD')
|
|
#
|
|
# if not usesExtraBits: #quick early create...
|
|
# landmeshname = "".join(["mc", corename])
|
|
# if landmeshname in bpy.data.meshes:
|
|
# return bpy.data.meshes[landmeshname]
|
|
# else:
|
|
# extraBits = None
|
|
#
|
|
# objectShape = "box" #but this can change based on extra data too...
|
|
# if len(bdat) > 4:
|
|
# objectShape = bdat[4]
|
|
#
|
|
# shapeParams = None
|
|
# if len(bdat) > 5: #and objectShape = 'insets'
|
|
# shapeParams = bdat[5]
|
|
#
|
|
# cycParams = None
|
|
# if OPTIONS['usecycles']:
|
|
# if len(bdat) > 6:
|
|
# cycParams = bdat[6]
|
|
# if cycParams is None:
|
|
# cycParams = {'emit': 0.0, 'stencil': False}
|
|
#
|
|
# nameVariant = ''
|
|
# if blockID in BLOCKVARIANTS:
|
|
# variants = BLOCKVARIANTS[blockID]
|
|
# if extraBits is not None and extraBits >= 0 and extraBits < len(variants):
|
|
# variantData = variants[extraBits]
|
|
# if len(variantData) > 0:
|
|
# nameVariant = variantData[0]
|
|
# #print("%d Block uses extra data: {%d}. So name variant is: %s" % (blockID, extraBits, nameVariant))
|
|
# #Now apply each available variant datum: RGB triple, texture faces, and blockbuild variation.
|
|
# if len(variantData) > 1: #read custom RGB
|
|
# colourtriple = variantData[1]
|
|
# if len(variantData) > 2:
|
|
# mcfaceindices = variantData[2]
|
|
# #mesh constructor...
|
|
# corename = "".join([corename, nameVariant])
|
|
# meshname = "".join(["mc", corename])
|
|
#
|
|
# dupblock = blockbuild.construct(blockID, corename, colourtriple, mcfaceindices, extraBits, objectShape, shapeParams, cycParams)
|
|
# blockname = dupblock.name
|
|
# landmeshname = "".join(["mc", blockname.replace('Block', '')])
|
|
#
|
|
# if landmeshname in bpy.data.meshes:
|
|
# return bpy.data.meshes[landmeshname]
|
|
#
|
|
# landmesh = bpy.data.meshes.new(landmeshname)
|
|
# landob = bpy.data.objects.new(landmeshname, landmesh)
|
|
# bpy.context.scene.objects.link(landob)
|
|
#
|
|
# global WORLD_ROOT #Will have been inited by now. Parent the land to it. (a bit messy, but... meh)
|
|
# landob.parent = WORLD_ROOT
|
|
# dupblock.parent = landob
|
|
# landob.dupli_type = "VERTS"
|
|
# return landmesh
|
|
|
|
|
|
# def slimeOn():
|
|
# """Creates the cloneable slime block (area marker) and a mesh to duplivert it."""
|
|
# if 'slimeChunks' in bpy.data.objects:
|
|
# return
|
|
#
|
|
# #Create cube! (maybe give it silly eyes...)
|
|
# #ensure 3d cursor at 0...
|
|
#
|
|
# bpy.ops.mesh.primitive_cube_add()
|
|
# slimeOb = bpy.context.object #get ref to last created ob.
|
|
# slimeOb.name = 'slimeMarker'
|
|
# #Make it chunk-sized. It starts 2x2x2
|
|
# bpy.ops.transform.resize(value=(8, 8, 8))
|
|
# bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
#
|
|
# # create material for the markers
|
|
# slimeMat = None
|
|
# smname = "mcSlimeMat"
|
|
# if smname in bpy.data.materials:
|
|
# slimeMat = bpy.data.materials[smname]
|
|
# else:
|
|
# slimeMat = bpy.data.materials.new(smname)
|
|
# #FIXME - hard code color
|
|
# slimeMat.diffuse_color = [86/256.0, 139.0/256.0, 72.0/256.0]
|
|
# slimeMat.diffuse_shader = 'OREN_NAYAR'
|
|
# slimeMat.diffuse_intensity = 0.8
|
|
# slimeMat.roughness = 0.909
|
|
# #slimeMat.use_shadeless = True #traceable false!
|
|
# slimeMat.use_transparency = True
|
|
# slimeMat.alpha = .25
|
|
#
|
|
# slimeOb.data.materials.append(slimeMat)
|
|
# slimeChunkmesh = bpy.data.meshes.new("slimeChunks")
|
|
# slimeChunkob = bpy.data.objects.new("slimeChunks", slimeChunkmesh)
|
|
# bpy.context.scene.objects.link(slimeChunkob)
|
|
# slimeOb.parent = slimeChunkob
|
|
# slimeChunkob.dupli_type = "VERTS"
|
|
# global WORLD_ROOT
|
|
# slimeChunkob.parent = WORLD_ROOT
|
|
#
|
|
#
|
|
# def batchSlimeChunks(slimes):
|
|
# #Populate all slime marker centres into the dupli-geom from pydata.
|
|
# me = bpy.data.meshes["slimeChunks"]
|
|
# me.from_pydata(slimes, [], [])
|
|
# me.update()
|
|
|
|
|
|
def getWorldSelectList():
|
|
worldList = []
|
|
MCSAVEPATH=sysutil.getMCSavePath()
|
|
if os.path.exists(MCSAVEPATH):
|
|
startpath = os.getcwd()
|
|
os.chdir(MCSAVEPATH)
|
|
saveList = os.listdir()
|
|
saveFolders = [f for f in saveList if os.path.isdir(f)]
|
|
wcount = 0
|
|
for sf in saveFolders:
|
|
if os.path.exists(sf + "/level.dat"):
|
|
#Read the actual world name (not just folder name)
|
|
wData = None
|
|
try:
|
|
with gzip.open(sf + '/level.dat', 'rb') as levelDat:
|
|
wData = nbtreader.readNBT(levelDat)
|
|
#catch errors if level.dat wasn't a gzip...
|
|
except IOError:
|
|
print("Unknown problem with level.dat format for %s" % sf)
|
|
continue
|
|
|
|
# FIXME - having a problem
|
|
try:
|
|
if 'LevelName' in wData.value['Data'].value:
|
|
wname = wData.value['Data'].value['LevelName'].value
|
|
else:
|
|
wname = "<no name>"
|
|
|
|
wsize = wData.value['Data'].value['SizeOnDisk'].value
|
|
readableSize = "(%0.1f)" % (wsize / (1024*1024))
|
|
worldList.append((sf, sf, wname + " " + readableSize))
|
|
wcount += 1
|
|
except KeyError:
|
|
print("key not found in %s" % wData.value['Data'])
|
|
os.chdir(startpath)
|
|
|
|
if worldList != []:
|
|
return worldList
|
|
else:
|
|
return None
|
|
|
|
|
|
def hasNether(worldFolder):
|
|
if worldFolder == "":
|
|
return False
|
|
worldList = []
|
|
MCSAVEPATH=sysutil.getMCSavePath()
|
|
if os.path.exists(MCSAVEPATH):
|
|
worldList = os.listdir(MCSAVEPATH)
|
|
if worldFolder in worldList:
|
|
wp = os.path.join(MCSAVEPATH, worldFolder, 'DIM-1')
|
|
return os.path.exists(wp)
|
|
#and: contains correct files? also check regions aren't empty.
|
|
return False
|
|
|
|
def hasEnd(worldFolder):
|
|
if worldFolder == "":
|
|
return False
|
|
worldList = []
|
|
MCSAVEPATH=sysutil.getMCSavePath()
|
|
if os.path.exists(MCSAVEPATH):
|
|
worldList = os.listdir(MCSAVEPATH)
|
|
if worldFolder in worldList:
|
|
wp = os.path.join(MCSAVEPATH, worldFolder, 'DIM1')
|
|
return os.path.exists(wp)
|
|
#and: contains correct files? also check regions aren't empty.
|
|
return False
|
|
|
|
|
|
def readMinecraftWorld(worldFolder, loadRadius, toggleOptions):
|
|
global unknownBlockIDs, wseed
|
|
global EXCLUDED_BLOCKS
|
|
global WORLD_ROOT
|
|
global OPTIONS, REPORTING
|
|
OPTIONS = toggleOptions
|
|
|
|
#timing/profiling:
|
|
global tChunkReadTimes
|
|
|
|
if worldFolder == "":
|
|
#World selected was blank. No saves. i.e. only when world list is empty
|
|
print("No valid saved worlds were available to load.")
|
|
return
|
|
|
|
# print("[!] OmitStone: ", toggleOptions['omitstone'])
|
|
if not OPTIONS['omitstone']:
|
|
EXCLUDED_BLOCKS = []
|
|
|
|
# print('[[[exluding these blocks: ', EXCLUDED_BLOCKS, ']]]')
|
|
worldList = []
|
|
|
|
MCSAVEPATH=sysutil.getMCSavePath()
|
|
if os.path.exists(MCSAVEPATH):
|
|
worldList = os.listdir(MCSAVEPATH)
|
|
#print("MC Path exists! %s" % os.listdir(MCPATH))
|
|
#wherever os was before, save it, and restore it after this completes.
|
|
os.chdir(MCSAVEPATH)
|
|
|
|
worldSelected = worldFolder
|
|
|
|
os.chdir(os.path.join(MCSAVEPATH, worldSelected))
|
|
|
|
# If there's a folder DIM-1 in the world folder, you've been to the Nether!
|
|
# ...And generated Nether regions.
|
|
if os.path.exists('DIM-1'):
|
|
if OPTIONS['loadnether']:
|
|
print('nether LOAD!')
|
|
else:
|
|
print('Nether is present, but not chosen to load.')
|
|
|
|
if os.path.exists('DIM1'):
|
|
if OPTIONS['loadend']:
|
|
print('load The End...')
|
|
else:
|
|
print('The End is present, but not chosen to load.')
|
|
|
|
#if the player didn't save out in those dimensions, we HAVE TO load at 3D cursor (or 0,0,0)
|
|
|
|
worldData = None
|
|
pSaveDim = None
|
|
worldFormat = 'mcregion' #assume initially
|
|
|
|
with gzip.open('level.dat', 'rb') as levelDat:
|
|
worldData = nbtreader.readNBT(levelDat)
|
|
#print(worlddata.printTree(0))
|
|
|
|
#Check if it's a multiplayer saved game (that's been moved into saves dir)
|
|
#These don't have the Player tag.
|
|
if 'Player' in worldData.value['Data'].value:
|
|
#It's singleplayer
|
|
pPos = [posFloat.value for posFloat in worldData.value['Data'].value['Player'].value['Pos'].value ] #in NBT, there's a lot of value...
|
|
pSaveDim = worldData.value['Data'].value['Player'].value['Dimension'].value
|
|
print('Player: '+str(pSaveDim)+', ppos: '+str(pPos))
|
|
else:
|
|
#It's multiplayer.
|
|
#Get SpawnX, SpawnY, SpawnZ and centre around those. OR
|
|
#TODO: Check for another subfolder: 'players'. Read each NBT .dat in
|
|
#there, create empties for all of them, but load around the first one.
|
|
spX = worldData.value['Data'].value['SpawnX'].value
|
|
spY = worldData.value['Data'].value['SpawnY'].value
|
|
spZ = worldData.value['Data'].value['SpawnZ'].value
|
|
pPos = [float(spX), float(spY), float(spZ)]
|
|
|
|
#create empty markers for each player.
|
|
#and: could it load multiplayer nether/end based on player loc?
|
|
|
|
if 'version' in worldData.value['Data'].value:
|
|
fmtVersion = worldData.value['Data'].value['version'].value
|
|
#19133 for Anvil. 19132 is McRegion.
|
|
if fmtVersion == MCREGION_VERSION_ID:
|
|
print("World is in McRegion format")
|
|
elif fmtVersion == ANVIL_VERSION_ID:
|
|
print("World is in Anvil format")
|
|
worldFormat = "anvil"
|
|
|
|
wseed = worldData.value['Data'].value['RandomSeed'].value #it's a Long
|
|
print("World Seed : %d" % (wseed)) # or self.report....
|
|
|
|
#NB: we load at cursor if player location undefined loading into Nether
|
|
if OPTIONS['atcursor'] or (OPTIONS['loadnether'] and (pSaveDim is None or int(pSaveDim) != -1)):
|
|
# cursorPos = bpy.context.scene.cursor_location
|
|
#that's an x,y,z vector (in Blender coords)
|
|
#convert to insane Minecraft coords! (Minecraft pos = -Y, Z, -X)
|
|
# pPos = [ -cursorPos[1], cursorPos[2], -cursorPos[0]]
|
|
pPos = [ 0, 0, 0]
|
|
|
|
if OPTIONS['loadnether']:
|
|
os.chdir(os.path.join("DIM-1", "region"))
|
|
elif OPTIONS['loadend']:
|
|
os.chdir(os.path.join("DIM1", "region"))
|
|
else:
|
|
os.chdir("region")
|
|
|
|
meshBuffer = {}
|
|
blockBuffer = {}
|
|
|
|
#Initialise the world root - an empty to parent all land objects to.
|
|
# WORLD_ROOT = bpy.data.objects.new(worldSelected, None) #,None => EMPTY!
|
|
# bpy.context.scene.objects.link(WORLD_ROOT)
|
|
# WORLD_ROOT.empty_draw_size = 2.0
|
|
# WORLD_ROOT.empty_draw_type = 'SPHERE'
|
|
|
|
regionfiles = []
|
|
regionreader = None
|
|
if worldFormat == 'mcregion':
|
|
regionfiles = [f for f in os.listdir() if f.endswith('.mcr')]
|
|
from mcregionreader import ChunkReader
|
|
regionreader = ChunkReader() #work it with the class, not an instance?
|
|
#all this importing is now very messy.
|
|
|
|
elif worldFormat == 'anvil':
|
|
regionfiles = [f for f in os.listdir() if f.endswith('.mca')]
|
|
from mcanvilreader import AnvilChunkReader
|
|
regionreader = AnvilChunkReader()
|
|
|
|
#except when loading nether...
|
|
playerChunk = toChunkPos(pPos[0], pPos[2]) # x, z
|
|
|
|
print("Loading %d blocks around centre." % loadRadius)
|
|
#loadRadius = 10 #Sane amount: 5 or 4.
|
|
|
|
# if not OPTIONS['atcursor']: #loading at player
|
|
# #Add an Empty to show where the player is. (+CENTRE CAMERA ON!)
|
|
# playerpos = bpy.data.objects.new('PlayerLoc', None)
|
|
# #set its coordinates...
|
|
# #convert Minecraft coordinate position of player into Blender coords:
|
|
# playerpos.location[0] = -pPos[2]
|
|
# playerpos.location[1] = -pPos[0]
|
|
# playerpos.location[2] = pPos[1]
|
|
# bpy.context.scene.objects.link(playerpos)
|
|
# playerpos.parent = WORLD_ROOT
|
|
|
|
#total chunk count across region files:
|
|
REPORTING['totalchunks'] = 0
|
|
|
|
# pX = int(playerChunk[0])
|
|
# pZ = int(playerChunk[1])
|
|
# ignore the player location, just load around origin
|
|
pX = 0
|
|
pZ = 0
|
|
|
|
print('Loading a square halfwidth of %d chunks around load position, so creating chunks: %d,%d to %d,%d' % (loadRadius, pX-loadRadius, pZ-loadRadius, pX+loadRadius, pZ+loadRadius))
|
|
|
|
# if (OPTIONS['showslimes']):
|
|
# # slimeOn()
|
|
# import slimes
|
|
# slimeBuffer = []
|
|
|
|
# FIXME - need deltaX/Y/Z to get array index
|
|
zeroAdjX = -1 * (pZ-loadRadius)
|
|
zeroAdjZ = -1 * (pX-loadRadius)
|
|
|
|
for z in range(pZ-loadRadius, pZ+loadRadius):
|
|
for x in range(pX-loadRadius, pX+loadRadius):
|
|
|
|
tChunk0 = datetime.datetime.now()
|
|
# if (OPTIONS['surfaceOnly']): # new method
|
|
# numElements=(loadRadius*2+1)*16 # chunks * blocks
|
|
# blockBuffer = npy.zeros((numElements,numElements,numElements))
|
|
#
|
|
# # FIXME - currently only supported by anvil reader
|
|
# regionreader.readChunk2(x,z, blockBuffer, zeroAdjX, zeroAdjZ)
|
|
# else: # old
|
|
regionreader.readChunk(x,z, meshBuffer) #may need to be further broken down to block level. maybe rename as loadChunk.
|
|
tChunk1 = datetime.datetime.now()
|
|
chunkTime = tChunk1 - tChunk0
|
|
tChunkReadTimes.append(chunkTime.total_seconds()) #tString = "%.2f seconds" % chunkTime.total_seconds() it's a float.
|
|
|
|
# if (OPTIONS['showslimes']):
|
|
# if slimes.isSlimeSpawn(wseed, x, z):
|
|
# slimeLoc = mcToBlendCoord((x,z), (8,8,8)) #(8,8,120)
|
|
# slimeLoc += Vector((0.5,0.5,-0.5))
|
|
# slimeBuffer.append(slimeLoc)
|
|
|
|
tBuild0 = datetime.datetime.now()
|
|
|
|
# batchBuild(meshBuffer)
|
|
# if (OPTIONS['showslimes']):
|
|
# batchSlimeChunks(slimeBuffer)
|
|
tBuild1 = datetime.datetime.now()
|
|
tBuildTime = tBuild1 - tBuild0
|
|
# print("Built meshes in %.2fs" % tBuildTime.total_seconds())
|
|
|
|
print("%s: loaded %d chunks" % (worldSelected, totalchunks))
|
|
if len(unknownBlockIDs) > 0:
|
|
print("Unknown new Minecraft datablock IDs encountered:")
|
|
print(" ".join(["%d" % bn for bn in unknownBlockIDs]))
|
|
|
|
#Viewport performance hides:
|
|
# if (OPTIONS['fasterViewport']):
|
|
# hideIfPresent('mcStone')
|
|
# hideIfPresent('mcDirt')
|
|
# hideIfPresent('mcSandstone')
|
|
# hideIfPresent('mcIronOre')
|
|
# hideIfPresent('mcGravel')
|
|
# hideIfPresent('mcCoalOre')
|
|
# hideIfPresent('mcBedrock')
|
|
# hideIfPresent('mcRedstoneOre')
|
|
|
|
#Profile/run stats:
|
|
chunkReadTotal = tChunkReadTimes[0]
|
|
for tdiff in tChunkReadTimes[1:]:
|
|
chunkReadTotal = chunkReadTotal + tdiff
|
|
print("Total read time: %.2fs" % chunkReadTotal) #I presume that's in seconds, ofc... hm.
|
|
chunkMRT = chunkReadTotal / len(tChunkReadTimes)
|
|
print("Mean chunk read time: %.2fs" % chunkMRT)
|
|
print("Block points processed: %d" % REPORTING['blocksread'])
|
|
blockMRT = REPORTING['blocksread'] / chunkReadTotal
|
|
print("Blocks read/second: %.2fs" % blockMRT)
|
|
|
|
# print("of those, verts dumped: %d" % REPORTING['blocksdropped'])
|
|
# if REPORTING['blocksread'] > 0:
|
|
# print("Difference (expected vertex count): %d" % (REPORTING['blocksread'] - REPORTING['blocksdropped']))
|
|
# print("Hollowing has made the scene %d%% lighter" % ((REPORTING['blocksdropped'] / REPORTING['blocksread']) * 100))
|
|
|
|
#increase viewport clip dist to see the world! (or decrease mesh sizes)
|
|
#bpy.types.Space...
|
|
#Actually: scale world root down to 0.05 by default?
|
|
|
|
# def hideIfPresent(mName):
|
|
# if mName in bpy.data.objects:
|
|
# bpy.data.objects[mName].hide = True
|
|
|
|
|
|
# Feature TODOs
|
|
# surface load (skin only, not block instances)
|
|
# torch, stairs, rails, redrep meshblocks.
|
|
# nether load
|
|
# mesh optimisations
|
|
# multiple loads per run -- need to name new meshes each time load performed, ie mcGrass.001
|
|
# ...
|