Initial working code
parent
c0e43df1df
commit
b98126a76f
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>mc2mt</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/${PROJECT_DIR_NAME}</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.0</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">python3</pydev_property>
|
||||
</pydev_project>
|
|
@ -0,0 +1,18 @@
|
|||
activeContentFilterList=*.makefile,makefile,*.Makefile,Makefile,Makefile.*,*.mk,MANIFEST.MF,.project
|
||||
addNewLine=true
|
||||
convertActionOnSaave=AnyEdit.CnvrtTabToSpaces
|
||||
eclipse.preferences.version=1
|
||||
fixLineDelimiters=false
|
||||
ignoreBlankLinesWhenTrimming=false
|
||||
inActiveContentFilterList=
|
||||
javaTabWidthForJava=true
|
||||
org.eclipse.jdt.ui.editor.tab.width=2
|
||||
projectPropsEnabled=false
|
||||
removeTrailingSpaces=true
|
||||
replaceAllSpaces=false
|
||||
replaceAllTabs=false
|
||||
saveAndAddLine=false
|
||||
saveAndConvert=false
|
||||
saveAndFixLineDelimiters=false
|
||||
saveAndTrim=false
|
||||
useModulo4Tabs=false
|
|
@ -0,0 +1,286 @@
|
|||
# io_import_minecraft
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# bl_info = {
|
||||
# "name": "Import: Minecraft b1.7+",
|
||||
# "description": "Importer for viewing Minecraft worlds",
|
||||
# "author": "Adam Crossan (acro)",
|
||||
# "version": (1,6,3),
|
||||
# "blender": (2, 6, 0),
|
||||
# "api": 41226,
|
||||
# "location": "File > Import > Minecraft",
|
||||
# "warning": '', # used for warning icon and text in addons panel
|
||||
# "wiki_url": "http://randomsamples.info/project/mineblend",
|
||||
# "category": "Import-Export"}
|
||||
|
||||
DEBUG_SCENE=False
|
||||
|
||||
# To support reload properly, try to access a package var, if it's there, reload everything
|
||||
#if "bpy" in locals():
|
||||
# import imp
|
||||
# if "mineregion" in locals():
|
||||
# imp.reload(mineregion)
|
||||
|
||||
#import bpy
|
||||
#from bpy.props import StringProperty, FloatProperty, IntProperty, BoolProperty, EnumProperty
|
||||
import imp
|
||||
import mineregion
|
||||
|
||||
#def setSceneProps(scn):
|
||||
# #Set up scene-level properties
|
||||
# bpy.types.Scene.MCLoadNether = BoolProperty(
|
||||
# name = "Load Nether",
|
||||
# description = "Load Nether (if present) instead of Overworld.",
|
||||
# default = False)
|
||||
|
||||
# scn['MCLoadNether'] = False
|
||||
# return
|
||||
#setSceneProps(bpy.context.scene)
|
||||
|
||||
# def createTestScene():
|
||||
# bpy.ops.scene.new(type='NEW')
|
||||
# bpy.context.scene.render.engine = 'CYCLES'
|
||||
# # plane
|
||||
# bpy.ops.mesh.primitive_plane_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# bpy.ops.transform.resize(value=(10,10,10), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# bpy.ops.material.new()
|
||||
# # cube
|
||||
# bpy.ops.mesh.primitive_cube_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# # FIXME - error
|
||||
# #bpy.context.space_data.context='MATERIAL'
|
||||
# bpy.ops.transform.translate(value=(0.55,0.17,1.14), constraint_axis=(False,False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# # set material to leaves?
|
||||
# bpy.ops.object.editmode_toggle()
|
||||
# bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
# # uv mapping - how do we tell blender?
|
||||
# #bpy.ops.transform.resize(value=(0.0368432,0.0368432,0.0368432), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# #bpy.ops.transform.translate(value=(-0.202301, 0.07906, 0), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional_falloff='SMOOTH', proportional_size=1)
|
||||
# bpy.ops.object.editmode_toggle()
|
||||
# # lights...
|
||||
# bpy.ops.object.lamp_add(type='SUN', view_align=True, location=(-8.12878,5.39259,9.70453), rotation=(-0.383973,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# # camera...
|
||||
# bpy.ops.object.camera_add(view_align=True, enter_editmode=False, location=(-8.12878,-9.13302,7.87796), rotation=(0,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# #bpy.context.space_data.context='CONSTRAINT'
|
||||
# bpy.ops.object.constraint_add(type='TRACK_TO')
|
||||
# bpy.context.object.constraints["Track To"].target = bpy.data.objects["Cube.001"]
|
||||
# bpy.context.object.constraints["Track To"].track_axis = 'TRACK_NEGATIVE_Z'
|
||||
# bpy.context.object.constraints["Track To"].up_axis = 'UP_Y'
|
||||
|
||||
|
||||
#Menu 'button' for the import menu (which calls the world selector)...
|
||||
# class MinecraftWorldSelector(bpy.types.Operator):
|
||||
#"""An operator defining a dialogue for choosing one on-disk Minecraft world to load.
|
||||
#This supplants the need to call the file selector, since
|
||||
"""Minecraft worlds require a preset specific folder structure of multiple files which cannot be selected singly."""
|
||||
|
||||
bl_idname = "mcraft.selectworld"
|
||||
bl_label = "Select Minecraft World"
|
||||
|
||||
#bl_space_type = "PROPERTIES"
|
||||
#Possible placements for these:
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
|
||||
#TODO: Make this much more intuitive for the user!
|
||||
#Would be better if could define min[x,y,z] and max[x,y,z] and load between these point
|
||||
mcLoadAtCursor = False #Loads as if 3D cursor offset in viewport was the player (load) position.
|
||||
mcLowLimit = 60 #The lowest depth layer to load. (High=256, Sea=64, Low=0)
|
||||
mcHighLimit = 128 #The highest layer to load. (High=256, Sea=64, Low=0)
|
||||
|
||||
mcLoadRadius = 10 # 'Load Radius - The half-width of the load range around load-pos.
|
||||
# e.g, 4 will load 9x9 chunks around the load centre
|
||||
# WARNING! Above 10, this gets slow and eats LOTS of memory!
|
||||
mcOmitStone = False # When True, do not import common blocks such as stone & dirt blocks (overworld) or netherrack (nether).
|
||||
mcDimenSelectList = '0' #Which dimension should be loaded? - '0'=Overworld; '1'=Nether, '2'=The End
|
||||
mcShowSlimeSpawns = False #'Display green markers showing slime-spawn locations
|
||||
mcUseCyclesMats = False #Blender Setting: Set up default materials for use with Cycles Render Engine instead of Blender Internal
|
||||
mcFasterViewport = False #Blender Setting: Disable display of common blocks (stone, dirt, etc.) in the viewport for better performance.
|
||||
mcSurfaceOnly = False #Omit underground blocks. Significantly better viewing and rendering performance.
|
||||
mcOmitMobs = True # When True, do not load mobs (creepers, skeletons, zombies, etc.) in world
|
||||
#may need to define loadnether and loadend as operators...?
|
||||
|
||||
# omit Dirt toggle option.
|
||||
|
||||
# height-limit option (only load down to a specific height) -- could be semi-dynamic and delve deeper when air value for the
|
||||
# column in question turns out to be lower than the loading threshold anyway.
|
||||
|
||||
#surfaceOnly ==> only load surface, discard underground areas. Doesn't count for nether.
|
||||
# Load Nether is, obviously, only available if selected world has nether)
|
||||
# Load End. Who has The End?! Not I!
|
||||
|
||||
#When specifying a property of type EnumProperty, ensure you call the constructing method correctly.
|
||||
#Note that items is a set of (identifier, value, description) triples, and default is a string unless you switch on options=ENUM_FLAG in which case make default a set of 1 string.
|
||||
#Need a better way to handle this variable: (possibly set it as a screen property)
|
||||
|
||||
# import mineregion
|
||||
wlist = mineregion.getWorldSelectList()
|
||||
if wlist is not None:
|
||||
revwlist = wlist[::-1]
|
||||
mcWorldSelectList = wlist[0][0] #Which Minecraft save should be loaded?
|
||||
else:
|
||||
mcWorldSelectList = 0 #Which Minecraft save should be loaded?
|
||||
|
||||
#TODO: on select, check presence of DIM-1 etc.
|
||||
print("List of Worlds: wlist:: ", wlist)
|
||||
|
||||
netherWorlds = [w[0] for w in wlist if mineregion.hasNether(w[0])]
|
||||
print("List of worlds with Nether: ", netherWorlds)
|
||||
|
||||
endWorlds = [e[0] for e in wlist if mineregion.hasEnd(e[0])]
|
||||
print("List of worlds with The End: ", endWorlds)
|
||||
|
||||
#my_worldlist = bpy.props.EnumProperty(items=[('0', "A", "The A'th item"), ('1', 'B', "Bth item"), ('2', 'C', "Cth item"), ('3', 'D', "dth item"), ('4', 'E', 'Eth item')][::-1], default='2', name="World", description="Which Minecraft save should be loaded?")
|
||||
|
||||
# def execute(self, context):
|
||||
#self.report({"INFO"}, "Loading world: " + str(self.mcWorldSelectList))
|
||||
#thread.sleep(30)
|
||||
#self.report({"WARNING"}, "Foo!")
|
||||
|
||||
#from . import mineregion
|
||||
# scn = context.scene
|
||||
|
||||
mcLoadDimenNether = True if mcDimenSelectList=='1' else False
|
||||
mcLoadDimenEnd = True if mcDimenSelectList=='2' else False
|
||||
# FIXME - when omitmobs is false, mobs will sometimes still not be imported (related to reload issue?)
|
||||
opts = {"omitstone": mcOmitStone, "showslimes": mcShowSlimeSpawns, "atcursor": mcLoadAtCursor,
|
||||
"highlimit": mcHighLimit, "lowlimit": mcLowLimit,
|
||||
"loadnether": mcLoadDimenNether, "loadend": mcLoadDimenEnd,
|
||||
"usecycles": mcUseCyclesMats, "omitmobs": mcOmitMobs,
|
||||
"fasterViewport": mcFasterViewport, "surfaceOnly": mcSurfaceOnly}
|
||||
print(str(mcWorldSelectList))
|
||||
print(str(opts))
|
||||
#get selected world name instead via bpy.ops.mcraft.worldselected -- the enumeration as a property/operator...?
|
||||
mineregion.readMinecraftWorld(str(mcWorldSelectList), mcLoadRadius, opts)
|
||||
# for s in bpy.context.area.spaces: # iterate all space in the active area
|
||||
# if s.type == "VIEW_3D": # check if space is a 3d-view
|
||||
# space = s
|
||||
# space.clip_end = 10000.0
|
||||
#run minecraftLoadChunks
|
||||
#if DEBUG_SCENE:
|
||||
# createTestScene()
|
||||
|
||||
# return {'FINISHED'}
|
||||
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# context.window_manager.invoke_props_dialog(self, width=350,height=250)
|
||||
# return {'RUNNING_MODAL'}
|
||||
#
|
||||
#
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
# col = layout.column()
|
||||
# col.label(text="Choose import options")
|
||||
#
|
||||
# row = col.row()
|
||||
# row.prop(self, "mcLoadAtCursor")
|
||||
#
|
||||
# row = col.row()
|
||||
#
|
||||
# sub = col.split(percentage=0.5)
|
||||
# colL = sub.column(align=True)
|
||||
# colL.prop(self, "mcShowSlimeSpawns")
|
||||
#
|
||||
# cycles = None
|
||||
# if hasattr(bpy.context.scene, 'cycles'):
|
||||
# cycles = bpy.context.scene.cycles
|
||||
# row2 = col.row()
|
||||
# if cycles is not None:
|
||||
# row2.active = (cycles is not None)
|
||||
# row2.prop(self, "mcUseCyclesMats")
|
||||
#
|
||||
# row3 = col.row()
|
||||
# row3.prop(self, "mcOmitStone")
|
||||
# row3.prop(self, "mcOmitMobs")
|
||||
#
|
||||
# row = col.row()
|
||||
# row.prop(self,"mcFasterViewport")
|
||||
# #row.prop(self,"mcSurfaceOnly")
|
||||
#
|
||||
# #if cycles:
|
||||
# #like this from properties_data_mesh.py:
|
||||
# ##layout = self.layout
|
||||
# ##mesh = context.mesh
|
||||
# ##split = layout.split()
|
||||
# ##col = split.column()
|
||||
# ##col.prop(mesh, "use_auto_smooth")
|
||||
# ##sub = col.column()
|
||||
# ##sub.active = mesh.use_auto_smooth
|
||||
# ##sub.prop(mesh, "auto_smooth_angle", text="Angle")
|
||||
# #row.operator(
|
||||
# #row.prop(self, "mcLoadEnd") #detect folder first (per world...)
|
||||
#
|
||||
# #label: "loading limits"
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcLowLimit")
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcHighLimit")
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcLoadRadius")
|
||||
#
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcDimenSelectList")
|
||||
# #col = layout.column()
|
||||
#
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcWorldSelectList")
|
||||
# #row.operator("mcraft.worldlist", icon='')
|
||||
# col = layout.column()
|
||||
|
||||
# def worldchange(self, context):
|
||||
# ##UPDATE (ie read then write back the value of) the property in the panel
|
||||
# #that needs to be updated. ensure it's in the scene so we can get it...
|
||||
# #bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
# #if the new world selected has nether, then update the nether field...
|
||||
# #in fact, maybe do that even if it doesn't.
|
||||
# #context.scene['MCLoadNether'] = True
|
||||
# return {'FINISHED'}
|
||||
#
|
||||
# class MineMenuItemOperator(bpy.types.Operator):
|
||||
# bl_idname = "mcraft.launchselector"
|
||||
# bl_label = "Needs label but label not used"
|
||||
#
|
||||
# def execute(self, context):
|
||||
# bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
# return {'FINISHED'}
|
||||
#
|
||||
# bpy.utils.register_class(MinecraftWorldSelector)
|
||||
# bpy.utils.register_class(MineMenuItemOperator)
|
||||
#bpy.utils.register_class(MCraft_PT_worldlist)
|
||||
|
||||
#Forumsearch tip!! FINDME:
|
||||
#Another way would be to update a property that is displayed in your panel via layout.prop(). AFAIK these are watched and cause a redraw on update.
|
||||
#
|
||||
# def mcraft_filemenu_func(self, context):
|
||||
# self.layout.operator("mcraft.launchselector", text="Minecraft (.region)", icon='MESH_CUBE')
|
||||
#
|
||||
#
|
||||
# def register():
|
||||
# #bpy.utils.register_module(__name__)
|
||||
# bpy.types.INFO_MT_file_import.append(mcraft_filemenu_func) # adds the operator action func to the filemenu
|
||||
#
|
||||
# def unregister():
|
||||
# #bpy.utils.unregister_module(__name__)
|
||||
# bpy.types.INFO_MT_file_import.remove(mcraft_filemenu_func) # removes the operator action func from the filemenu
|
||||
#
|
||||
# if __name__ == "__main__":
|
||||
# register()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,302 @@
|
|||
# io_import_minecraft
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
bl_info = {
|
||||
"name": "Import: Minecraft b1.7+",
|
||||
"description": "Importer for viewing Minecraft worlds",
|
||||
"author": "Adam Crossan (acro)",
|
||||
"version": (1,6,3),
|
||||
"blender": (2, 6, 0),
|
||||
"api": 41226,
|
||||
"location": "File > Import > Minecraft",
|
||||
"warning": '', # used for warning icon and text in addons panel
|
||||
"wiki_url": "http://randomsamples.info/project/mineblend",
|
||||
"category": "Import-Export"}
|
||||
|
||||
DEBUG_SCENE=False
|
||||
|
||||
# To support reload properly, try to access a package var, if it's there, reload everything
|
||||
if "bpy" in locals():
|
||||
import imp
|
||||
if "mineregion" in locals():
|
||||
imp.reload(mineregion)
|
||||
|
||||
import bpy
|
||||
from bpy.props import StringProperty, FloatProperty, IntProperty, BoolProperty, EnumProperty
|
||||
from . import mineregion
|
||||
|
||||
#def setSceneProps(scn):
|
||||
# #Set up scene-level properties
|
||||
# bpy.types.Scene.MCLoadNether = BoolProperty(
|
||||
# name = "Load Nether",
|
||||
# description = "Load Nether (if present) instead of Overworld.",
|
||||
# default = False)
|
||||
|
||||
# scn['MCLoadNether'] = False
|
||||
# return
|
||||
#setSceneProps(bpy.context.scene)
|
||||
|
||||
def createTestScene():
|
||||
bpy.ops.scene.new(type='NEW')
|
||||
bpy.context.scene.render.engine = 'CYCLES'
|
||||
# plane
|
||||
bpy.ops.mesh.primitive_plane_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
bpy.ops.transform.resize(value=(10,10,10), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.material.new()
|
||||
# cube
|
||||
bpy.ops.mesh.primitive_cube_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# FIXME - error
|
||||
#bpy.context.space_data.context='MATERIAL'
|
||||
bpy.ops.transform.translate(value=(0.55,0.17,1.14), constraint_axis=(False,False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# set material to leaves?
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
# uv mapping - how do we tell blender?
|
||||
#bpy.ops.transform.resize(value=(0.0368432,0.0368432,0.0368432), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
#bpy.ops.transform.translate(value=(-0.202301, 0.07906, 0), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional_falloff='SMOOTH', proportional_size=1)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
# lights...
|
||||
bpy.ops.object.lamp_add(type='SUN', view_align=True, location=(-8.12878,5.39259,9.70453), rotation=(-0.383973,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# camera...
|
||||
bpy.ops.object.camera_add(view_align=True, enter_editmode=False, location=(-8.12878,-9.13302,7.87796), rotation=(0,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
#bpy.context.space_data.context='CONSTRAINT'
|
||||
bpy.ops.object.constraint_add(type='TRACK_TO')
|
||||
bpy.context.object.constraints["Track To"].target = bpy.data.objects["Cube.001"]
|
||||
bpy.context.object.constraints["Track To"].track_axis = 'TRACK_NEGATIVE_Z'
|
||||
bpy.context.object.constraints["Track To"].up_axis = 'UP_Y'
|
||||
|
||||
|
||||
#Menu 'button' for the import menu (which calls the world selector)...
|
||||
class MinecraftWorldSelector(bpy.types.Operator):
|
||||
"""An operator defining a dialogue for choosing one on-disk Minecraft world to load.
|
||||
This supplants the need to call the file selector, since Minecraft worlds require
|
||||
a preset specific folder structure of multiple files which cannot be selected singly."""
|
||||
|
||||
bl_idname = "mcraft.selectworld"
|
||||
bl_label = "Select Minecraft World"
|
||||
|
||||
#bl_space_type = "PROPERTIES"
|
||||
#Possible placements for these:
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
mcLoadAtCursor = bpy.props.BoolProperty(name='Use 3D Cursor as Player', description='Loads as if 3D cursor offset in viewport was the player (load) position.', default=False)
|
||||
|
||||
#TODO: Make this much more intuitive for the user!
|
||||
mcLowLimit = bpy.props.IntProperty(name='Load Floor', description='The lowest depth layer to load. (High=256, Sea=64, Low=0)', min=0, max=256, step=1, default=60, subtype='UNSIGNED')
|
||||
mcHighLimit = bpy.props.IntProperty(name='Load Ceiling', description='The highest layer to load. (High=256, Sea=64, Low=0)', min=0, max=256, step=1, default=128, subtype='UNSIGNED')
|
||||
|
||||
mcLoadRadius = bpy.props.IntProperty(name='Load Radius', description="""The half-width of the load range around load-pos.
|
||||
e.g, 4 will load 9x9 chunks around the load centre
|
||||
WARNING! Above 10, this gets slow and eats LOTS of memory!""", min=1, max=50, step=1, default=5, subtype='UNSIGNED') #soft_min, soft_max?
|
||||
#optimiser algorithms/detail omissions
|
||||
|
||||
mcOmitStone = bpy.props.BoolProperty(name='Omit common blocks', description='When checked, do not import common blocks such as stone & dirt blocks (overworld) or netherrack (nether). Significantly improves performance... good for preview imports.', default=False)
|
||||
|
||||
mcDimenSelectList = bpy.props.EnumProperty(items=[('0', 'Overworld', 'Overworld'), ('1', 'Nether', 'Nether'), ('2', 'The End', 'The End')][::1], name="Dimension", description="Which dimension should be loaded?") #default='0'
|
||||
|
||||
mcShowSlimeSpawns = bpy.props.BoolProperty(name='Slime Spawns', description='Display green markers showing slime-spawn locations', default=False)
|
||||
|
||||
mcUseCyclesMats = bpy.props.BoolProperty(name='Use Cycles', description='Set up default materials for use with Cycles Render Engine instead of Blender Internal', default=True)
|
||||
|
||||
mcFasterViewport = bpy.props.BoolProperty(name='Faster viewport', description='Disable display of common blocks (stone, dirt, etc.) in the viewport for better performance. These block types will still be rendered.', default=True)
|
||||
|
||||
mcSurfaceOnly = bpy.props.BoolProperty(name='Surface only', description='Omit underground blocks. Significantly better viewing and rendering performance.', default=False) # FIXME - not yet
|
||||
|
||||
# TODO
|
||||
#mcGroupBlocks = bpy.props.BoolProperty(name='Group blocks', description='Omit underground blocks. Significantly better viewing and rendering performance.', default=True)
|
||||
|
||||
mcOmitMobs = bpy.props.BoolProperty(name='Omit Mobs', description='When checked, do not load mobs (creepers, skeletons, zombies, etc.) in world', default=True)
|
||||
#may need to define loadnether and loadend as operators...?
|
||||
|
||||
# omit Dirt toggle option.
|
||||
|
||||
# height-limit option (only load down to a specific height) -- could be semi-dynamic and delve deeper when air value for the
|
||||
# column in question turns out to be lower than the loading threshold anyway.
|
||||
|
||||
#surfaceOnly ==> only load surface, discard underground areas. Doesn't count for nether.
|
||||
# Load Nether is, obviously, only available if selected world has nether)
|
||||
# Load End. Who has The End?! Not I!
|
||||
|
||||
#When specifying a property of type EnumProperty, ensure you call the constructing method correctly.
|
||||
#Note that items is a set of (identifier, value, description) triples, and default is a string unless you switch on options=ENUM_FLAG in which case make default a set of 1 string.
|
||||
#Need a better way to handle this variable: (possibly set it as a screen property)
|
||||
|
||||
from . import mineregion
|
||||
wlist = mineregion.getWorldSelectList()
|
||||
if wlist is not None:
|
||||
revwlist = wlist[::-1]
|
||||
#temp debug REMOVE!
|
||||
###dworld = None
|
||||
###wnamelist = [w[0] for w in revwlist]
|
||||
###if "AnviliaWorld" in wnamelist:
|
||||
#####build the item for it to be default-selected...? Or work out if ENUM_FLAG is on?
|
||||
### dworld = "%d" % wnamelist.index("AnviliaWorld") #set(["AnviliaWorld"])
|
||||
###if dworld is None:
|
||||
mcWorldSelectList = bpy.props.EnumProperty(items=wlist[::-1], name="World", description="Which Minecraft save should be loaded?") #default='0', update=worldchange
|
||||
###else:
|
||||
### mcWorldSelectList = bpy.props.EnumProperty(items=wlist[::-1], name="World", description="Which Minecraft save should be loaded?", default=dworld) #, options={'ENUM_FLAG'}
|
||||
else:
|
||||
mcWorldSelectList = bpy.props.EnumProperty(items=[], name="World", description="Which Minecraft save should be loaded?") #, update=worldchange
|
||||
|
||||
#TODO: on select, check presence of DIM-1 etc.
|
||||
#print("wlist:: ", wlist)
|
||||
netherWorlds = [w[0] for w in wlist if mineregion.hasNether(w[0])]
|
||||
#print("List of worlds with Nether: ", netherWorlds)
|
||||
|
||||
endWorlds = [e[0] for e in wlist if mineregion.hasEnd(e[0])]
|
||||
#print("List of worlds with The End: ", endWorlds)
|
||||
|
||||
#my_worldlist = bpy.props.EnumProperty(items=[('0', "A", "The A'th item"), ('1', 'B', "Bth item"), ('2', 'C', "Cth item"), ('3', 'D', "dth item"), ('4', 'E', 'Eth item')][::-1], default='2', name="World", description="Which Minecraft save should be loaded?")
|
||||
|
||||
def execute(self, context):
|
||||
#self.report({"INFO"}, "Loading world: " + str(self.mcWorldSelectList))
|
||||
#thread.sleep(30)
|
||||
#self.report({"WARNING"}, "Foo!")
|
||||
|
||||
#from . import mineregion
|
||||
scn = context.scene
|
||||
|
||||
mcLoadDimenNether = True if (self.mcDimenSelectList=='1') else False
|
||||
mcLoadDimenEnd = True if (self.mcDimenSelectList=='2') else False
|
||||
# FIXME - when omitmobs is unchecked, mobs will sometimes still not be imported (related to reload issue?)
|
||||
opts = {"omitstone": self.mcOmitStone, "showslimes": self.mcShowSlimeSpawns, "atcursor": self.mcLoadAtCursor,
|
||||
"highlimit": self.mcHighLimit, "lowlimit": self.mcLowLimit,
|
||||
"loadnether": mcLoadDimenNether, "loadend": mcLoadDimenEnd,
|
||||
"usecycles": self.mcUseCyclesMats, "omitmobs": self.mcOmitMobs,
|
||||
"fasterViewport": self.mcFasterViewport, "surfaceOnly": self.mcSurfaceOnly}
|
||||
#print(str(opts))
|
||||
#get selected world name instead via bpy.ops.mcraft.worldselected -- the enumeration as a property/operator...?
|
||||
mineregion.readMinecraftWorld(str(self.mcWorldSelectList), self.mcLoadRadius, opts)
|
||||
for s in bpy.context.area.spaces: # iterate all space in the active area
|
||||
if s.type == "VIEW_3D": # check if space is a 3d-view
|
||||
space = s
|
||||
space.clip_end = 10000.0
|
||||
#run minecraftLoadChunks
|
||||
if DEBUG_SCENE:
|
||||
createTestScene()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.invoke_props_dialog(self, width=350,height=250)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Choose import options")
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, "mcLoadAtCursor")
|
||||
|
||||
row = col.row()
|
||||
|
||||
sub = col.split(percentage=0.5)
|
||||
colL = sub.column(align=True)
|
||||
colL.prop(self, "mcShowSlimeSpawns")
|
||||
|
||||
cycles = None
|
||||
if hasattr(bpy.context.scene, 'cycles'):
|
||||
cycles = bpy.context.scene.cycles
|
||||
row2 = col.row()
|
||||
if cycles is not None:
|
||||
row2.active = (cycles is not None)
|
||||
row2.prop(self, "mcUseCyclesMats")
|
||||
|
||||
row3 = col.row()
|
||||
row3.prop(self, "mcOmitStone")
|
||||
row3.prop(self, "mcOmitMobs")
|
||||
|
||||
row = col.row()
|
||||
row.prop(self,"mcFasterViewport")
|
||||
#row.prop(self,"mcSurfaceOnly")
|
||||
|
||||
#if cycles:
|
||||
#like this from properties_data_mesh.py:
|
||||
##layout = self.layout
|
||||
##mesh = context.mesh
|
||||
##split = layout.split()
|
||||
##col = split.column()
|
||||
##col.prop(mesh, "use_auto_smooth")
|
||||
##sub = col.column()
|
||||
##sub.active = mesh.use_auto_smooth
|
||||
##sub.prop(mesh, "auto_smooth_angle", text="Angle")
|
||||
#row.operator(
|
||||
#row.prop(self, "mcLoadEnd") #detect folder first (per world...)
|
||||
|
||||
#label: "loading limits"
|
||||
row = layout.row()
|
||||
row.prop(self, "mcLowLimit")
|
||||
row = layout.row()
|
||||
row.prop(self, "mcHighLimit")
|
||||
row = layout.row()
|
||||
row.prop(self, "mcLoadRadius")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(self, "mcDimenSelectList")
|
||||
#col = layout.column()
|
||||
|
||||
row = layout.row()
|
||||
row.prop(self, "mcWorldSelectList")
|
||||
#row.operator("mcraft.worldlist", icon='')
|
||||
col = layout.column()
|
||||
|
||||
def worldchange(self, context):
|
||||
##UPDATE (ie read then write back the value of) the property in the panel
|
||||
#that needs to be updated. ensure it's in the scene so we can get it...
|
||||
#bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
#if the new world selected has nether, then update the nether field...
|
||||
#in fact, maybe do that even if it doesn't.
|
||||
#context.scene['MCLoadNether'] = True
|
||||
return {'FINISHED'}
|
||||
|
||||
class MineMenuItemOperator(bpy.types.Operator):
|
||||
bl_idname = "mcraft.launchselector"
|
||||
bl_label = "Needs label but label not used"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
return {'FINISHED'}
|
||||
|
||||
bpy.utils.register_class(MinecraftWorldSelector)
|
||||
bpy.utils.register_class(MineMenuItemOperator)
|
||||
#bpy.utils.register_class(MCraft_PT_worldlist)
|
||||
|
||||
#Forumsearch tip!! FINDME:
|
||||
#Another way would be to update a property that is displayed in your panel via layout.prop(). AFAIK these are watched and cause a redraw on update.
|
||||
|
||||
def mcraft_filemenu_func(self, context):
|
||||
self.layout.operator("mcraft.launchselector", text="Minecraft (.region)", icon='MESH_CUBE')
|
||||
|
||||
|
||||
def register():
|
||||
#bpy.utils.register_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.append(mcraft_filemenu_func) # adds the operator action func to the filemenu
|
||||
|
||||
def unregister():
|
||||
#bpy.utils.unregister_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.remove(mcraft_filemenu_func) # removes the operator action func from the filemenu
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,285 @@
|
|||
# io_import_minecraft
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# bl_info = {
|
||||
# "name": "Import: Minecraft b1.7+",
|
||||
# "description": "Importer for viewing Minecraft worlds",
|
||||
# "author": "Adam Crossan (acro)",
|
||||
# "version": (1,6,3),
|
||||
# "blender": (2, 6, 0),
|
||||
# "api": 41226,
|
||||
# "location": "File > Import > Minecraft",
|
||||
# "warning": '', # used for warning icon and text in addons panel
|
||||
# "wiki_url": "http://randomsamples.info/project/mineblend",
|
||||
# "category": "Import-Export"}
|
||||
|
||||
DEBUG_SCENE=False
|
||||
|
||||
# To support reload properly, try to access a package var, if it's there, reload everything
|
||||
#if "bpy" in locals():
|
||||
# import imp
|
||||
# if "mineregion" in locals():
|
||||
# imp.reload(mineregion)
|
||||
|
||||
#import bpy
|
||||
#from bpy.props import StringProperty, FloatProperty, IntProperty, BoolProperty, EnumProperty
|
||||
import imp
|
||||
import mineregion
|
||||
|
||||
#def setSceneProps(scn):
|
||||
# #Set up scene-level properties
|
||||
# bpy.types.Scene.MCLoadNether = BoolProperty(
|
||||
# name = "Load Nether",
|
||||
# description = "Load Nether (if present) instead of Overworld.",
|
||||
# default = False)
|
||||
|
||||
# scn['MCLoadNether'] = False
|
||||
# return
|
||||
#setSceneProps(bpy.context.scene)
|
||||
|
||||
# def createTestScene():
|
||||
# bpy.ops.scene.new(type='NEW')
|
||||
# bpy.context.scene.render.engine = 'CYCLES'
|
||||
# # plane
|
||||
# bpy.ops.mesh.primitive_plane_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# bpy.ops.transform.resize(value=(10,10,10), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# bpy.ops.material.new()
|
||||
# # cube
|
||||
# bpy.ops.mesh.primitive_cube_add(radius=1, view_align=True, enter_editmode=False, location=(0,0,0), rotation=(0,0,0), layers = (True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# # FIXME - error
|
||||
# #bpy.context.space_data.context='MATERIAL'
|
||||
# bpy.ops.transform.translate(value=(0.55,0.17,1.14), constraint_axis=(False,False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# # set material to leaves?
|
||||
# bpy.ops.object.editmode_toggle()
|
||||
# bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
# # uv mapping - how do we tell blender?
|
||||
# #bpy.ops.transform.resize(value=(0.0368432,0.0368432,0.0368432), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)
|
||||
# #bpy.ops.transform.translate(value=(-0.202301, 0.07906, 0), constraint_axis=(False,False,False), constraint_orientation='GLOBAL', mirror=False, proportional_falloff='SMOOTH', proportional_size=1)
|
||||
# bpy.ops.object.editmode_toggle()
|
||||
# # lights...
|
||||
# bpy.ops.object.lamp_add(type='SUN', view_align=True, location=(-8.12878,5.39259,9.70453), rotation=(-0.383973,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# # camera...
|
||||
# bpy.ops.object.camera_add(view_align=True, enter_editmode=False, location=(-8.12878,-9.13302,7.87796), rotation=(0,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
||||
# #bpy.context.space_data.context='CONSTRAINT'
|
||||
# bpy.ops.object.constraint_add(type='TRACK_TO')
|
||||
# bpy.context.object.constraints["Track To"].target = bpy.data.objects["Cube.001"]
|
||||
# bpy.context.object.constraints["Track To"].track_axis = 'TRACK_NEGATIVE_Z'
|
||||
# bpy.context.object.constraints["Track To"].up_axis = 'UP_Y'
|
||||
|
||||
|
||||
#Menu 'button' for the import menu (which calls the world selector)...
|
||||
# class MinecraftWorldSelector(bpy.types.Operator):
|
||||
#"""An operator defining a dialogue for choosing one on-disk Minecraft world to load.
|
||||
#This supplants the need to call the file selector, since
|
||||
"""Minecraft worlds require a preset specific folder structure of multiple files which cannot be selected singly."""
|
||||
|
||||
bl_idname = "mcraft.selectworld"
|
||||
bl_label = "Select Minecraft World"
|
||||
|
||||
#bl_space_type = "PROPERTIES"
|
||||
#Possible placements for these:
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
|
||||
#TODO: Make this much more intuitive for the user!
|
||||
#Would be better if could define min[x,y,z] and max[x,y,z] and load between these point
|
||||
mcLoadAtCursor = False #Loads as if 3D cursor offset in viewport was the player (load) position.
|
||||
mcLowLimit = 60 #The lowest depth layer to load. (High=256, Sea=64, Low=0)
|
||||
mcHighLimit = 128 #The highest layer to load. (High=256, Sea=64, Low=0)
|
||||
|
||||
mcLoadRadius = 5 # 'Load Radius - The half-width of the load range around load-pos.
|
||||
# e.g, 4 will load 9x9 chunks around the load centre
|
||||
# WARNING! Above 10, this gets slow and eats LOTS of memory!
|
||||
mcOmitStone = False # When True, do not import common blocks such as stone & dirt blocks (overworld) or netherrack (nether).
|
||||
mcDimenSelectList = '0' #Which dimension should be loaded? - 0=Overworld; 1=Nether, 2=The End
|
||||
mcShowSlimeSpawns = False #'Display green markers showing slime-spawn locations
|
||||
mcUseCyclesMats = False #Blender Setting: Set up default materials for use with Cycles Render Engine instead of Blender Internal
|
||||
mcFasterViewport = False #Blender Setting: Disable display of common blocks (stone, dirt, etc.) in the viewport for better performance.
|
||||
mcSurfaceOnly = False #Omit underground blocks. Significantly better viewing and rendering performance.
|
||||
mcOmitMobs = True # When True, do not load mobs (creepers, skeletons, zombies, etc.) in world
|
||||
#may need to define loadnether and loadend as operators...?
|
||||
|
||||
# omit Dirt toggle option.
|
||||
|
||||
# height-limit option (only load down to a specific height) -- could be semi-dynamic and delve deeper when air value for the
|
||||
# column in question turns out to be lower than the loading threshold anyway.
|
||||
|
||||
#surfaceOnly ==> only load surface, discard underground areas. Doesn't count for nether.
|
||||
# Load Nether is, obviously, only available if selected world has nether)
|
||||
# Load End. Who has The End?! Not I!
|
||||
|
||||
#When specifying a property of type EnumProperty, ensure you call the constructing method correctly.
|
||||
#Note that items is a set of (identifier, value, description) triples, and default is a string unless you switch on options=ENUM_FLAG in which case make default a set of 1 string.
|
||||
#Need a better way to handle this variable: (possibly set it as a screen property)
|
||||
|
||||
# import mineregion
|
||||
wlist = mineregion.getWorldSelectList()
|
||||
if wlist is not None:
|
||||
revwlist = wlist[::-1]
|
||||
mcWorldSelectList = 0 #Which Minecraft save should be loaded?
|
||||
else:
|
||||
mcWorldSelectList = 0 #Which Minecraft save should be loaded?
|
||||
|
||||
#TODO: on select, check presence of DIM-1 etc.
|
||||
print("List of Worlds: wlist:: ", wlist)
|
||||
|
||||
netherWorlds = [w[0] for w in wlist if mineregion.hasNether(w[0])]
|
||||
print("List of worlds with Nether: ", netherWorlds)
|
||||
|
||||
endWorlds = [e[0] for e in wlist if mineregion.hasEnd(e[0])]
|
||||
print("List of worlds with The End: ", endWorlds)
|
||||
|
||||
#my_worldlist = bpy.props.EnumProperty(items=[('0', "A", "The A'th item"), ('1', 'B', "Bth item"), ('2', 'C', "Cth item"), ('3', 'D', "dth item"), ('4', 'E', 'Eth item')][::-1], default='2', name="World", description="Which Minecraft save should be loaded?")
|
||||
|
||||
# def execute(self, context):
|
||||
#self.report({"INFO"}, "Loading world: " + str(self.mcWorldSelectList))
|
||||
#thread.sleep(30)
|
||||
#self.report({"WARNING"}, "Foo!")
|
||||
|
||||
#from . import mineregion
|
||||
# scn = context.scene
|
||||
|
||||
mcLoadDimenNether = True if mcDimenSelectList=='1' else False
|
||||
mcLoadDimenEnd = True if mcDimenSelectList=='2' else False
|
||||
# FIXME - when omitmobs is false, mobs will sometimes still not be imported (related to reload issue?)
|
||||
opts = {"omitstone": mcOmitStone, "showslimes": mcShowSlimeSpawns, "atcursor": mcLoadAtCursor,
|
||||
"highlimit": mcHighLimit, "lowlimit": mcLowLimit,
|
||||
"loadnether": mcLoadDimenNether, "loadend": mcLoadDimenEnd,
|
||||
"usecycles": mcUseCyclesMats, "omitmobs": mcOmitMobs,
|
||||
"fasterViewport": mcFasterViewport, "surfaceOnly": mcSurfaceOnly}
|
||||
#print(str(opts))
|
||||
#get selected world name instead via bpy.ops.mcraft.worldselected -- the enumeration as a property/operator...?
|
||||
mineregion.readMinecraftWorld(str(mcWorldSelectList), mcLoadRadius, opts)
|
||||
# for s in bpy.context.area.spaces: # iterate all space in the active area
|
||||
# if s.type == "VIEW_3D": # check if space is a 3d-view
|
||||
# space = s
|
||||
# space.clip_end = 10000.0
|
||||
#run minecraftLoadChunks
|
||||
#if DEBUG_SCENE:
|
||||
# createTestScene()
|
||||
|
||||
# return {'FINISHED'}
|
||||
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# context.window_manager.invoke_props_dialog(self, width=350,height=250)
|
||||
# return {'RUNNING_MODAL'}
|
||||
#
|
||||
#
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
# col = layout.column()
|
||||
# col.label(text="Choose import options")
|
||||
#
|
||||
# row = col.row()
|
||||
# row.prop(self, "mcLoadAtCursor")
|
||||
#
|
||||
# row = col.row()
|
||||
#
|
||||
# sub = col.split(percentage=0.5)
|
||||
# colL = sub.column(align=True)
|
||||
# colL.prop(self, "mcShowSlimeSpawns")
|
||||
#
|
||||
# cycles = None
|
||||
# if hasattr(bpy.context.scene, 'cycles'):
|
||||
# cycles = bpy.context.scene.cycles
|
||||
# row2 = col.row()
|
||||
# if cycles is not None:
|
||||
# row2.active = (cycles is not None)
|
||||
# row2.prop(self, "mcUseCyclesMats")
|
||||
#
|
||||
# row3 = col.row()
|
||||
# row3.prop(self, "mcOmitStone")
|
||||
# row3.prop(self, "mcOmitMobs")
|
||||
#
|
||||
# row = col.row()
|
||||
# row.prop(self,"mcFasterViewport")
|
||||
# #row.prop(self,"mcSurfaceOnly")
|
||||
#
|
||||
# #if cycles:
|
||||
# #like this from properties_data_mesh.py:
|
||||
# ##layout = self.layout
|
||||
# ##mesh = context.mesh
|
||||
# ##split = layout.split()
|
||||
# ##col = split.column()
|
||||
# ##col.prop(mesh, "use_auto_smooth")
|
||||
# ##sub = col.column()
|
||||
# ##sub.active = mesh.use_auto_smooth
|
||||
# ##sub.prop(mesh, "auto_smooth_angle", text="Angle")
|
||||
# #row.operator(
|
||||
# #row.prop(self, "mcLoadEnd") #detect folder first (per world...)
|
||||
#
|
||||
# #label: "loading limits"
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcLowLimit")
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcHighLimit")
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcLoadRadius")
|
||||
#
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcDimenSelectList")
|
||||
# #col = layout.column()
|
||||
#
|
||||
# row = layout.row()
|
||||
# row.prop(self, "mcWorldSelectList")
|
||||
# #row.operator("mcraft.worldlist", icon='')
|
||||
# col = layout.column()
|
||||
|
||||
# def worldchange(self, context):
|
||||
# ##UPDATE (ie read then write back the value of) the property in the panel
|
||||
# #that needs to be updated. ensure it's in the scene so we can get it...
|
||||
# #bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
# #if the new world selected has nether, then update the nether field...
|
||||
# #in fact, maybe do that even if it doesn't.
|
||||
# #context.scene['MCLoadNether'] = True
|
||||
# return {'FINISHED'}
|
||||
#
|
||||
# class MineMenuItemOperator(bpy.types.Operator):
|
||||
# bl_idname = "mcraft.launchselector"
|
||||
# bl_label = "Needs label but label not used"
|
||||
#
|
||||
# def execute(self, context):
|
||||
# bpy.ops.mcraft.selectworld('INVOKE_DEFAULT')
|
||||
# return {'FINISHED'}
|
||||
#
|
||||
# bpy.utils.register_class(MinecraftWorldSelector)
|
||||
# bpy.utils.register_class(MineMenuItemOperator)
|
||||
#bpy.utils.register_class(MCraft_PT_worldlist)
|
||||
|
||||
#Forumsearch tip!! FINDME:
|
||||
#Another way would be to update a property that is displayed in your panel via layout.prop(). AFAIK these are watched and cause a redraw on update.
|
||||
#
|
||||
# def mcraft_filemenu_func(self, context):
|
||||
# self.layout.operator("mcraft.launchselector", text="Minecraft (.region)", icon='MESH_CUBE')
|
||||
#
|
||||
#
|
||||
# def register():
|
||||
# #bpy.utils.register_module(__name__)
|
||||
# bpy.types.INFO_MT_file_import.append(mcraft_filemenu_func) # adds the operator action func to the filemenu
|
||||
#
|
||||
# def unregister():
|
||||
# #bpy.utils.unregister_module(__name__)
|
||||
# bpy.types.INFO_MT_file_import.remove(mcraft_filemenu_func) # removes the operator action func from the filemenu
|
||||
#
|
||||
# if __name__ == "__main__":
|
||||
# register()
|
|
@ -0,0 +1,486 @@
|
|||
import os
|
||||
#import bpy
|
||||
|
||||
from struct import unpack #, error as StructError
|
||||
import nbtreader, mcregionreader
|
||||
from mineregion import OPTIONS, EXCLUDED_BLOCKS, BLOCKDATA, REPORTING, unknownBlockIDs, WORLD_ROOT
|
||||
##..yuck: they're immutable and don't return properly except for the dict-type ones. Get rid of this in next cleanup.
|
||||
|
||||
from math import floor
|
||||
|
||||
class AnvilChunkReader(mcregionreader.ChunkReader):
|
||||
|
||||
#readBlock( bX, bZ (by?) ... ignoring 'region' boundaries and chunk boundaries? We need an ignore-chunk-boundaries level of abstraction
|
||||
|
||||
def getSingleBlock(chunkXZ, blockXYZ): #returns the value and extradata bits for a single block of given absolute x,y,z block coords within chunk cx,cz. or None if area not generated.
|
||||
#y is value from 0..255
|
||||
cx, cy = chunkXZ
|
||||
bX,bY,bZ = blockXYZ
|
||||
rX = floor(cx / 32) # is this the same as >> 8 ??
|
||||
rZ = floor(cz / 32)
|
||||
rHdrOffset = ((cx % 32) + (cz % 32) * 32) * 4
|
||||
rFile = "r.%d.%d.mca" % (rx, rz)
|
||||
if not os.path.exists(rFile):
|
||||
return None
|
||||
with open(rFile, 'rb') as regionfile:
|
||||
regionfile.seek(rheaderoffset)
|
||||
cheadr = regionfile.read(4)
|
||||
dataoffset = unpack(">i", b'\x00'+cheadr[0:3])[0]
|
||||
chunksectorcount = cheadr[3]
|
||||
if dataoffset == 0 and chunksectorcount == 0:
|
||||
return None #Region exists, but the chunk we're after was never created within it.
|
||||
else:
|
||||
#possibly check for cached chunk data here, under the cx,cz in a list of already-loaded sets.
|
||||
chunkdata = AnvilChunkReader._readChunkData(regionfile, dataoffset, chunksectorcount)
|
||||
chunkLvl = chunkdata.value['Level'].value
|
||||
sections = chunkLvl['Sections'].value
|
||||
#each section is a 16x16x16 piece of chunk, with a Y-byte from 0-15, so that the 'y' value is 16*that + in-section-Y-value
|
||||
#some sections can be skipped, so we must iterate to find the right one with the 'Y' we expect.
|
||||
bSection = bY / 16
|
||||
sect = None
|
||||
for section in sections:
|
||||
secY = section.value['Y'].value
|
||||
if secY == bSection:
|
||||
sect = section.value
|
||||
if sect is None:
|
||||
return None
|
||||
blockData = sec['Blocks'].value #a TAG_Byte_Array value (bytes object). Blocks is 16x16 bytes
|
||||
extraData = sec['Data'].value #BlockLight, Data and SkyLight are 16x16 "4-bit cell" additional data arrays.
|
||||
sY = dY % 16
|
||||
blockIndex = (sY * 16 + dZ) * 16 + dX
|
||||
blockID = blockData[ blockIndex ]
|
||||
return blockID #, extravalue)
|
||||
#NB: this can be made massively more efficient by storing 4 'neighbour chunk' data reads for every chunk properly processed.
|
||||
#Don't need to do diagonals, even.
|
||||
|
||||
|
||||
|
||||
|
||||
def readChunk2(self, chunkPosX, chunkPosZ, blockBuffer, zeroAdjX, zeroAdjY):
|
||||
# FIXME - implement me!
|
||||
return
|
||||
|
||||
def readChunk(self, chunkPosX, chunkPosZ, vertexBuffer): # aka "readChunkFromRegion" ...
|
||||
"""Loads chunk located at the X,Z chunk location provided."""
|
||||
|
||||
global REPORTING
|
||||
|
||||
#region containing a given chunk is found thusly: floor of c over 32
|
||||
regionX = floor(chunkPosX / 32)
|
||||
regionZ = floor(chunkPosZ / 32)
|
||||
|
||||
rheaderoffset = ((chunkPosX % 32) + (chunkPosZ % 32) * 32) * 4
|
||||
|
||||
#print("Reading chunk %d,%d from region %d,%d" %(chunkPosX, chunkPosZ, regionX,regionZ))
|
||||
|
||||
rfileName = "r.%d.%d.mca" % (regionX, regionZ)
|
||||
if not os.path.exists(rfileName):
|
||||
#Can't load: it doesn't exist!
|
||||
print("No such region generated.")
|
||||
return
|
||||
|
||||
with open(rfileName, 'rb') as regfile:
|
||||
# header for the chunk we want is at...
|
||||
#The location in the region file of a chunk at (x, z) (in chunk coordinates) can be found at byte offset 4 * ((x mod 32) + (z mod 32) * 32) in its McRegion file.
|
||||
#Its timestamp can be found 4096 bytes later in the file
|
||||
regfile.seek(rheaderoffset)
|
||||
cheadr = regfile.read(4)
|
||||
dataoffset = unpack(">i", b'\x00'+cheadr[0:3])[0]
|
||||
chunksectorcount = cheadr[3]
|
||||
|
||||
if dataoffset == 0 and chunksectorcount == 0:
|
||||
pass
|
||||
#print("Region exists, but chunk has never been created within it.")
|
||||
else:
|
||||
chunkdata = AnvilChunkReader._readChunkData(regfile, dataoffset, chunksectorcount) #todo: rename that function!
|
||||
#Geometry creation! etc... If surface only, can get heights etc from lightarray?
|
||||
|
||||
#top level tag in NBT is an unnamed TAG_Compound, for some reason, containing a named TAG_Compound "Level"
|
||||
chunkLvl = chunkdata.value['Level'].value
|
||||
#chunkXPos = chunkLvl['xPos'].value
|
||||
#chunkZPos = chunkLvl['zPos'].value
|
||||
#print("Reading blocks for chunk: (%d, %d)\n" % (chunkXPos, chunkZPos))
|
||||
AnvilChunkReader._readBlocks(chunkLvl, vertexBuffer)
|
||||
#print("Loaded chunk %d,%d" % (chunkPosX,chunkPosZ))
|
||||
|
||||
REPORTING['totalchunks'] += 1
|
||||
|
||||
|
||||
def _readChunkData(bstream, chunkOffset, chunkSectorCount): #rename this!
|
||||
#get the datastring out of the file...
|
||||
import io, zlib
|
||||
|
||||
#cf = open(fname, 'rb')
|
||||
initialPos = bstream.tell()
|
||||
|
||||
cstart = chunkOffset * 4096 #4 kiB
|
||||
clen = chunkSectorCount * 4096
|
||||
bstream.seek(cstart) #this bstream is the region file
|
||||
|
||||
chunkHeaderAndData = bstream.read(clen)
|
||||
|
||||
#chunk header stuff is:
|
||||
# 4 bytes: length (of remaining data)
|
||||
# 1 byte : compression type (1 - gzip - unused; 2 - zlib: it should always be this in actual fact)
|
||||
# then the rest, is length-1 bytes of compressed (zlib) NBT data.
|
||||
|
||||
chunkDLength = unpack(">i", chunkHeaderAndData[0:4])[0]
|
||||
chunkDCompression = chunkHeaderAndData[4]
|
||||
if chunkDCompression != 2:
|
||||
print("Not a zlib-compressed chunk!?")
|
||||
raise StringError() #MinecraftSomethingError, perhaps.
|
||||
|
||||
chunkZippedBytes = chunkHeaderAndData[5:]
|
||||
|
||||
#could/should check that chunkZippedBytes is same length as chunkDLength-1.
|
||||
|
||||
#put the regionfile byte stream back to where it started:
|
||||
bstream.seek(initialPos)
|
||||
|
||||
#Read the compressed chunk data
|
||||
zipper = zlib.decompressobj()
|
||||
chunkData = zipper.decompress(chunkZippedBytes)
|
||||
chunkDataAsFile = io.BytesIO(chunkData)
|
||||
chunkNBT = nbtreader.readNBT(chunkDataAsFile)
|
||||
|
||||
return chunkNBT
|
||||
|
||||
def getSectionBlock(blockLoc, sectionDict):
|
||||
"""Fetches a block from section NBT data."""
|
||||
(bX,bY,bZ) = blockLoc
|
||||
secY = bY >> 4 #/ 16
|
||||
if secY not in sectionDict:
|
||||
return None
|
||||
sect = sectionDict[secY]
|
||||
sY = bY & 0xf #mod 16
|
||||
bIndex = (sY * 16 + bZ) * 16 + bX
|
||||
#bitshift, or run risk of int casts
|
||||
dat = sect['Blocks'].value
|
||||
return dat[bIndex]
|
||||
|
||||
#Hollow volumes optimisation (version1: in-chunk only)
|
||||
def _isExposedBlock(blockCoord, chunkXZ, secBlockData, sectionDict, blockID, skyHighLimit, depthLimit): #another param: neighbourChunkData[] - a 4-list of NBT stuff...
|
||||
(dX,dY,dZ) = blockCoord
|
||||
#fail-fast. checks if all ortho adjacent neighbours fall inside this chunk.
|
||||
#EASY! Because it's 0-15 for both X and Z. For Y, we're iterating upward,
|
||||
#so get the previous value (the block below) passed in.
|
||||
|
||||
if blockID == 18: #leaves #and glass? and other exemptions?
|
||||
return True
|
||||
|
||||
if dX == 0 or dX == 15 or dY == 0 or dZ == 0 or dZ == 15:
|
||||
#get neighbour directly
|
||||
return True #instead, check neigbouring chunks...
|
||||
|
||||
|
||||
#we can no longer get the block below or above easily as we might be iterating +x, -16x, or +z at any given step.
|
||||
if dY == skyHighLimit or dY == depthLimit:
|
||||
return True
|
||||
|
||||
ySect = dY / 16 ## all this dividing integers by 16! I ask you! (>> 4)!
|
||||
yBoff = dY % 16 ## &= 0x0f
|
||||
#if you are on a section boundary, need next section for block above. else
|
||||
|
||||
#GLOBALS (see readBlocks, below)
|
||||
CHUNKSIZE_X = 16 #static consts - global?
|
||||
CHUNKSIZE_Z = 16
|
||||
#new layout goes YZX. improves compression, apparently.
|
||||
##_Y_SHIFT = 7 # 2**7 is 128. use for fast multiply
|
||||
##_YZ_SHIFT = 11 #16 * 128 is 2048, which is 2**11
|
||||
|
||||
#check above (Y+1)
|
||||
#either it's in the same section (quick/easy lookup) or it's in another section (still quite easy - next array over)
|
||||
#or, it's in another chunk. in which case, check chunkreadcache for the 4 adjacent. Failing this, it's the worse case and
|
||||
#we need to read into a whole new chunk data grab.
|
||||
if yBoff == 15:
|
||||
upBlock = AnvilChunkReader.getSectionBlock((dX,dY+1,dZ), sectionDict)
|
||||
if upBlock != blockID:
|
||||
return True
|
||||
else:
|
||||
#get it from current section
|
||||
upIndex = ((yBoff+1) * 16 + dZ) * 16 + dX
|
||||
upBlock = secBlockData[ upIndex ]
|
||||
if upBlock != blockID:
|
||||
return True
|
||||
|
||||
#Check below (Y-1):
|
||||
if yBoff == 0:
|
||||
downBlock = AnvilChunkReader.getSectionBlock((dX,dY-1,dZ), sectionDict)
|
||||
if downBlock != blockID:
|
||||
return True
|
||||
else:
|
||||
downIndex = ((yBoff-1) * 16 + dZ) * 16 + dX
|
||||
dnBlock = secBlockData[downIndex]
|
||||
if dnBlock != blockID:
|
||||
return True
|
||||
|
||||
#Have checked above and below; now check all sides. Same section, but maybe different chunks...
|
||||
#Check X-1 (leftward)
|
||||
leftIndex = (yBoff * 16 + dZ) * 16 + (dX-1)
|
||||
#ngbIndex = dY + (dZ << _Y_SHIFT) + ((dX-1) << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
try:
|
||||
neighbour = secBlockData[leftIndex]
|
||||
except IndexError:
|
||||
print("Bogus index cockup: %d. Blockdata len is 16x16x16 bytes (4096)." % leftIndex)
|
||||
quit()
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check X+1
|
||||
rightIndex = (yBoff * 16 + dZ) * 16 + (dX+1)
|
||||
#ngbIndex = dY + (dZ << _Y_SHIFT) + ((dX+1) << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = secBlockData[rightIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check Z-1
|
||||
ngbIndex = (yBoff * 16 + (dZ-1)) * 16 + dX
|
||||
#ngbIndex = dY + ((dZ-1) << _Y_SHIFT) + (dX << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = secBlockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check Z+1
|
||||
ngbIndex = (yBoff * 16 + (dZ+1)) * 16 + dX
|
||||
#ngbIndex = dY + ((dZ+1) << _Y_SHIFT) + (dX << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = secBlockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
#nb: 0 is bottom bedrock, 256 (255?) is top of sky. Sea is 64.
|
||||
def _readBlocks(chunkLevelData, vertexBuffer):
|
||||
"""readBlocks(chunkLevelData) -> takes a named TAG_Compound 'Level' containing a chunk's Anvil Y-Sections, each of which 0-15 has blocks, data, heightmap, xpos,zpos, etc.
|
||||
Adds the data points into a 'vertexBuffer' which is a per-named-type dictionary of ????'s. That later is made into Blender geometry via from_pydata."""
|
||||
#TODO: also TileEntities and Entities. Entities will generally be an empty list.
|
||||
#TileEntities are needed for some things to define fully...
|
||||
|
||||
#TODO: Keep an 'adjacent chunk cache' for neighbourhood is-exposed checks.
|
||||
|
||||
global unknownBlockIDs, OPTIONS, REPORTING
|
||||
|
||||
#chunkLocation = 'xPos' 'zPos' ...
|
||||
chunkX = chunkLevelData['xPos'].value
|
||||
chunkZ = chunkLevelData['zPos'].value
|
||||
biomes = chunkLevelData['Biomes'].value #yields a TAG_Byte_Array value (bytes object) of len 256 (16x16)
|
||||
#heightmap = chunkLevelData['HeightMap'].value
|
||||
#'TileEntities' -- surely need this for piston data and stuff, no?
|
||||
|
||||
entities = chunkLevelData['Entities'].value # load ze sheeps!! # a list of tag-compounds.
|
||||
#omitmobs = OPTIONS['omitmobs']
|
||||
if not OPTIONS['omitmobs']:
|
||||
AnvilChunkReader._loadEntities(entities)
|
||||
|
||||
skyHighLimit = OPTIONS['highlimit']
|
||||
depthLimit = OPTIONS['lowlimit']
|
||||
|
||||
CHUNKSIZE_X = 16
|
||||
CHUNKSIZE_Z = 16
|
||||
SECTNSIZE_Y = 16
|
||||
|
||||
##_Y_SHIFT = 7 # 2**7 is 128. use for fast multiply
|
||||
##_YZ_SHIFT = 11 #16 * 128 is 2048, which is 2**11
|
||||
sections = chunkLevelData['Sections'].value
|
||||
|
||||
#each section is a 16x16x16 piece of chunk, with a Y-byte from 0-15, so that the 'y' value is 16*that + in-section-Y-value
|
||||
|
||||
#iterate through all block Y values from bedrock to max height (minor step through X,Z.)
|
||||
#bearing in mind some can be skipped out.
|
||||
|
||||
#sectionDict => a dictionary of sections, indexed by Y.
|
||||
sDict = {}
|
||||
for section in sections:
|
||||
sY = section.value['Y'].value
|
||||
sDict[sY] = section.value
|
||||
|
||||
for section in sections:
|
||||
sec = section.value
|
||||
secY = sec['Y'].value * SECTNSIZE_Y
|
||||
|
||||
#if (secY + 16) < lowlimit, skip this section. no need to load it.
|
||||
if (secY+16 < depthLimit):
|
||||
continue
|
||||
|
||||
if (secY > skyHighLimit):
|
||||
return
|
||||
|
||||
#Now actually proceed with adding in the section's block data.
|
||||
blockData = sec['Blocks'].value #yields a TAG_Byte_Array value (bytes object). Blocks is 16x16 bytes
|
||||
extraData = sec['Data'].value #BlockLight, Data and SkyLight are 16x16 "4-bit cell" additional data arrays.
|
||||
|
||||
#get starting Y from heightmap, ignoring excess height iterations...
|
||||
#heightByte = heightMap[dX + (dZ << 4)] # z * 16
|
||||
#heightByte = 255 #quickFix: start from tip top, for now
|
||||
#if heightByte > skyHighLimit:
|
||||
# heightByte = skyHighLimit
|
||||
|
||||
#go y 0 to 16...
|
||||
for sy in range(16):
|
||||
dY = secY + sy
|
||||
|
||||
if dY < depthLimit:
|
||||
continue
|
||||
if dY > skyHighLimit:
|
||||
return
|
||||
|
||||
# dataX will be dX, blender X will be bX.
|
||||
for dZ in range(CHUNKSIZE_Z):
|
||||
#print("looping chunk z %d" % dZ)
|
||||
for dX in range(CHUNKSIZE_X):
|
||||
#oneBlockLeft = 0 #data value of the block 1 back to the left (-X) from where we are now. (for neighbour comparisons)
|
||||
#ie microcached 'last item read'. needs tweaked for chunk crossover...
|
||||
|
||||
##blockIndex = (dZ << _Y_SHIFT) + (dX << _YZ_SHIFT) # max number of bytes in a chunk is 32768. this is coming in at 32839 for XYZ: (15,71,8)
|
||||
##blockIndex = (dZ * 16) + dX
|
||||
#YZX ((y * 16 + z) * 16 + x
|
||||
blockIndex = (sy * 16 + dZ) * 16 + dX
|
||||
blockID = blockData[ blockIndex ]
|
||||
|
||||
#except IndexError:
|
||||
# print("X:%d Y:%d Z %d, blockID from before: %d, cx,cz: %d,%d. Blockindex: %d" % (dX,dY,dZ,blockID,chunkX,chunkZ, blockIndex))
|
||||
# raise IndexError
|
||||
|
||||
#create this block in the output!
|
||||
if blockID != 0 and blockID not in EXCLUDED_BLOCKS: # 0 is air
|
||||
REPORTING['blocksread'] += 1
|
||||
|
||||
#hollowness test:
|
||||
if blockID in BLOCKDATA:
|
||||
# if AnvilChunkReader._isExposedBlock((dX,dY,dZ), (chunkX, chunkZ), blockData, sDict, blockID, skyHighLimit, depthLimit):
|
||||
#TODO: Make better version of this check, counting across chunks and regions.
|
||||
#Load extra data (if applicable to blockID):
|
||||
#if it has extra data, grab 4 bits from extraData
|
||||
datOffset = (int(blockIndex /2)) #divided by 2
|
||||
datHiBits = blockIndex % 2 #odd or even, will be hi or low nibble
|
||||
extraDatByte = extraData[datOffset] # should be a byte of which we only want part.
|
||||
hiMask = 0b11110000
|
||||
loMask = 0b00001111
|
||||
extraValue = None
|
||||
if datHiBits:
|
||||
#get high 4, and shift right 4.
|
||||
extraValue = loMask & (extraDatByte >> 4)
|
||||
else:
|
||||
#mask hi 4 off.
|
||||
extraValue = extraDatByte & loMask
|
||||
#create block in corresponding blockmesh
|
||||
AnvilChunkReader.createBlock(blockID, (chunkX, chunkZ), (dX,dY,dZ), extraValue, vertexBuffer)
|
||||
# else:
|
||||
# REPORTING['blocksdropped'] += 1
|
||||
else:
|
||||
#print("Unrecognised Block ID: %d" % blockID)
|
||||
#createUnknownMeshBlock()
|
||||
unknownBlockIDs.add(blockID)
|
||||
|
||||
#TAG_Byte("Y"): 0
|
||||
#TAG_Byte_Array("Blocks"): [4096 bytes array]
|
||||
#TAG_Byte_Array("BlockLight"): [2048 bytes array]
|
||||
#TAG_Byte_Array("Data"): [2048 bytes array]
|
||||
#TAG_Byte_Array("SkyLight"): [2048 bytes array]
|
||||
##TAG_Byte_Array("Add"): [2048 bytes array] ##Only appears if it's needed!
|
||||
|
||||
def _loadEntities(entities):
|
||||
global WORLD_ROOT
|
||||
for e in entities:
|
||||
eData = e.value
|
||||
|
||||
etypename = eData['id'].value #eg 'Sheep'
|
||||
ename = "en%sMarker" % etypename
|
||||
epos = [p.value for p in eData['Pos'].value] #list[3] of double
|
||||
erot = [r.value for r in eData['Rotation'].value] #list[2] of float ([0] orientation (angle round Z-axis) and [1] 0.00, probably y-tilt.
|
||||
|
||||
#instantiate and rotate-in a placeholder object for this (and add to controlgroup or parent to something handy.)
|
||||
#translate to blend coords, too.
|
||||
entMarker = bpy.data.objects.new(ename, None)
|
||||
#set its coordinates...
|
||||
#convert Minecraft coordinate position of player into Blender coords:
|
||||
entMarker.location[0] = -epos[2]
|
||||
entMarker.location[1] = -epos[0]
|
||||
entMarker.location[2] = epos[1]
|
||||
|
||||
#also, set its z-rotation to erot[0]...
|
||||
#entMarker.rotation[2] = erot[0]
|
||||
|
||||
bpy.context.scene.objects.link(entMarker)
|
||||
entMarker.parent = WORLD_ROOT
|
||||
|
||||
|
||||
|
||||
##NB! Future blocks will require the Add tag to be checked and mixed in!
|
||||
#Each section also has a "Add" tag, which is a DataLayer byte array just like
|
||||
#"Data". The "Add" tag is not included in the converter since the old format
|
||||
#never had block ids above 255. This extra tag is created whenever a block
|
||||
#requires it, so the getTile() method needs to check if the array exists and
|
||||
#then combine it with the default block data. In other words,
|
||||
#blockId = (add << 8) + baseId.
|
||||
|
||||
# Blocks, Data, Skylight, ... heightmap
|
||||
#Blocks contain the block ids; Data contains the extra info: 4 bits of lighting info + 4 bits of 'extra fields'
|
||||
# eg Lamp direction, crop wetness, etc.
|
||||
# Heightmap gives us quick access to the top surface of everything - ie optimise out iterating through all sky blocks.
|
||||
|
||||
#To access a specific block from either the block or data array from XYZ coordinates, use the following formula:
|
||||
# Index = x + (y * Height + z) * Width
|
||||
|
||||
##Note that the old format is XZY ((x * 16 + z) * 16 + y) and the new format is YZX ((y * 16 + z) * 16 + x)
|
||||
|
||||
#16x16 (256) ints of heightmap data. Each int records the lowest level
|
||||
#in each column where the light from the sky is at full strength. Speeds up
|
||||
#computing of the SkyLight. Note: This array's indexes are ordered Z,X
|
||||
#whereas the other array indexes are ordered X,Z,Y.
|
||||
|
||||
#loadedData -> we buffer everything into lists, then batch-create the
|
||||
#vertices later. This makes the model build in Blender many, many times faster
|
||||
|
||||
#list of named, distinct material meshes. add vertices to each, only in batches.
|
||||
#Optimisation: 'Hollow volumes': only add if there is at least 1 orthogonal non-same-type neighbour.
|
||||
#Aggressive optimisation: only load if there is 1 air orthogonal neighbour (or transparent materials).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 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.
|
||||
# And 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 createBlock(blockID, chunkPos, blockPos, extraBlockData, vertBuffer):
|
||||
"""adds a vertex to the blockmesh for blockID in the relevant location."""
|
||||
print("AnvilChunkReader.createBlock")
|
||||
print("blockID: " + str(blockID))
|
||||
print("chunkPos: " + str(chunkPos))
|
||||
print("blockPos: " + str(blockPos))
|
||||
print("extraBlockData: " + str(extraBlockData))
|
||||
print("")
|
||||
# print("vertBuffer: " + str(vertBuffer))
|
||||
|
||||
# chunkpos is X,Z; blockpos is x,y,z for block.
|
||||
# mesh = getMCBlockType(blockID, extraBlockData) #this could be inefficient. Perhaps create all the types at the start, then STOP MAKING THIS CHECK!
|
||||
# if mesh is None:
|
||||
# return
|
||||
#
|
||||
# typeName = mesh.name
|
||||
# vertex = mcToBlendCoord(chunkPos, blockPos)
|
||||
#
|
||||
# if typeName in vertBuffer:
|
||||
# vertBuffer[typeName].append(vertex)
|
||||
# else:
|
||||
# vertBuffer[typeName] = [vertex]
|
||||
|
||||
#xyz is local to the 'stone' mesh for example. but that's from 0 (world).
|
||||
#regionfile can be found from chunkPos.
|
||||
#Chunkpos is an X,Z pair.
|
||||
#Blockpos is an X,Y,Z triple - within chunk.
|
|
@ -0,0 +1,295 @@
|
|||
# FIXME - obsolete and likely no longer working as of 1.6.3... any reason to keep around?
|
||||
|
||||
import os
|
||||
|
||||
from struct import unpack #, error as StructError
|
||||
import nbtreader
|
||||
from mineregion import OPTIONS, EXCLUDED_BLOCKS, BLOCKDATA, REPORTING, unknownBlockIDs #, getMCBlockType, mcToBlendCoord #yuck!
|
||||
##..yuck: they're immutable and don't return properly except for the dict-type ones. Get rid of this in next cleanup.
|
||||
|
||||
class ChunkReader:
|
||||
|
||||
#readBlock( cX,cZ,(sY?), (bX,bY,bZ) ... ) ignoring 'region' boundaries and chunk boundaries? We need an ignore-chunk-boundaries level of abstraction
|
||||
|
||||
def readChunk(self, chunkPosX, chunkPosZ, vertexBuffer): # aka "readChunkFromRegion" ...
|
||||
"""Loads chunk located at the X,Z chunk location provided."""
|
||||
from math import floor
|
||||
global REPORTING
|
||||
|
||||
#region containing a given chunk is found thusly: floor of c over 32
|
||||
regionX = floor(chunkPosX / 32)
|
||||
regionZ = floor(chunkPosZ / 32)
|
||||
|
||||
rheaderoffset = ((chunkPosX % 32) + (chunkPosZ % 32) * 32) * 4
|
||||
|
||||
#print("Reading chunk %d,%d from region %d,%d" %(chunkPosX, chunkPosZ, regionX,regionZ))
|
||||
|
||||
rfileName = "r.%d.%d.mcr" % (regionX, regionZ)
|
||||
if not os.path.exists(rfileName):
|
||||
#Can't load: it doesn't exist!
|
||||
print("No such region generated.")
|
||||
return
|
||||
|
||||
with open(rfileName, 'rb') as regfile:
|
||||
# header for the chunk we want is at...
|
||||
#The location in the region file of a chunk at (x, z) (in chunk coordinates) can be found at byte offset 4 * ((x mod 32) + (z mod 32) * 32) in its McRegion file.
|
||||
#Its timestamp can be found 4096 bytes later in the file
|
||||
regfile.seek(rheaderoffset)
|
||||
cheadr = regfile.read(4)
|
||||
dataoffset = unpack(">i", b'\x00'+cheadr[0:3])[0]
|
||||
chunksectorcount = cheadr[3]
|
||||
|
||||
if dataoffset == 0 and chunksectorcount == 0:
|
||||
pass
|
||||
#print("Region exists, but chunk has never been created within it.")
|
||||
else:
|
||||
chunkdata = self._readChunkData(regfile, dataoffset, chunksectorcount) #todo: rename that function!
|
||||
#Geometry creation! etc... If surface only, can get heights etc from lightarray?
|
||||
|
||||
#top level tag in NBT is an unnamed TAG_Compound, for some reason, containing a named TAG_Compound "Level"
|
||||
chunkLvl = chunkdata.value['Level'].value
|
||||
#chunkXPos = chunkLvl['xPos'].value
|
||||
#chunkZPos = chunkLvl['zPos'].value
|
||||
#print("Reading blocks for chunk: (%d, %d)\n" % (chunkXPos, chunkZPos))
|
||||
ChunkReader.readBlocks(chunkLvl, vertexBuffer)
|
||||
#print("Loaded chunk %d,%d" % (chunkPosX,chunkPosZ))
|
||||
|
||||
REPORTING['totalchunks'] += 1
|
||||
|
||||
|
||||
def _readChunkData(self, bstream, chunkOffset, chunkSectorCount): #rename this!
|
||||
#get the datastring out of the file...
|
||||
import io, zlib
|
||||
|
||||
#cf = open(fname, 'rb')
|
||||
initialPos = bstream.tell()
|
||||
|
||||
cstart = chunkOffset * 4096 #4 kiB
|
||||
clen = chunkSectorCount * 4096
|
||||
bstream.seek(cstart) #this bstream is the region file
|
||||
|
||||
chunkHeaderAndData = bstream.read(clen)
|
||||
|
||||
#chunk header stuff is:
|
||||
# 4 bytes: length (of remaining data)
|
||||
# 1 byte : compression type (1 - gzip - unused; 2 - zlib: it should always be this in actual fact)
|
||||
# then the rest, is length-1 bytes of compressed (zlib) NBT data.
|
||||
|
||||
chunkDLength = unpack(">i", chunkHeaderAndData[0:4])[0]
|
||||
chunkDCompression = chunkHeaderAndData[4]
|
||||
if chunkDCompression != 2:
|
||||
print("Not a zlib-compressed chunk!?")
|
||||
raise StringError() #MinecraftSomethingError, perhaps.
|
||||
|
||||
chunkZippedBytes = chunkHeaderAndData[5:]
|
||||
|
||||
#could/should check that chunkZippedBytes is same length as chunkDLength-1.
|
||||
|
||||
#put the regionfile byte stream back to where it started:
|
||||
bstream.seek(initialPos)
|
||||
|
||||
#Read the compressed chunk data
|
||||
zipper = zlib.decompressobj()
|
||||
chunkData = zipper.decompress(chunkZippedBytes)
|
||||
chunkDataAsFile = io.BytesIO(chunkData)
|
||||
chunkNBT = nbtreader.readNBT(chunkDataAsFile)
|
||||
|
||||
return chunkNBT
|
||||
|
||||
|
||||
#Hollow volumes optimisation (version1: in-chunk only)
|
||||
def _isExposedBlock(dX,dY,dZ, blockData, blockID, idAbove, skyHighLimit, depthLimit):
|
||||
#fail-fast. checks if all ortho adjacent neighbours fall inside this chunk.
|
||||
#EASY! Because it's 0-15 for both X and Z. For Y, we're iterating downward,
|
||||
#so get the previous value (the block above) passed in.
|
||||
|
||||
if dX == 0 or dX == 15 or dY == 0 or dZ == 0 or dZ == 15 or blockID == 18: #leaves
|
||||
return True
|
||||
|
||||
if idAbove != blockID:
|
||||
return True
|
||||
|
||||
if dY == skyHighLimit or dY == depthLimit:
|
||||
return True
|
||||
|
||||
#GLOBALS (see readBlocks, below)
|
||||
CHUNKSIZE_X = 16 #static consts - global?
|
||||
CHUNKSIZE_Y = 128
|
||||
CHUNKSIZE_Z = 16
|
||||
_Y_SHIFT = 7 # 2**7 is 128. use for fast multiply
|
||||
_YZ_SHIFT = 11 #16 * 128 is 2048, which is 2**11
|
||||
|
||||
#Check below:
|
||||
ngbIndex = dY-1 + (dZ << _Y_SHIFT) + (dX << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = blockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Now checked above and below. Check all sides.
|
||||
#Check -X
|
||||
ngbIndex = dY + (dZ << _Y_SHIFT) + ((dX-1) << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = blockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check +X
|
||||
ngbIndex = dY + (dZ << _Y_SHIFT) + ((dX+1) << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = blockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check -Z
|
||||
ngbIndex = dY + ((dZ-1) << _Y_SHIFT) + (dX << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = blockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
#Check +Z
|
||||
ngbIndex = dY + ((dZ+1) << _Y_SHIFT) + (dX << _YZ_SHIFT) #Check this lookup in readBlocks, below! Can it go o.o.b.?
|
||||
neighbour = blockData[ngbIndex]
|
||||
if neighbour != blockID:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
#nb: 0 is bottom bedrock, 128 is top of sky. Sea is 64.
|
||||
def readBlocks(chunkLevelData, vertexBuffer):
|
||||
"""readBlocks(chunkLevelData) -> takes a named TAG_Compound 'Level' containing a chunk's blocks, data, heightmap, xpos,zpos, etc.
|
||||
Adds the data points into a 'vertexBuffer' which is a per-named-type dictionary of ????'s. That later is made into Blender geometry via from_pydata."""
|
||||
#TODO: also TileEntities and Entities. Entities will generally be an empty list.
|
||||
#TileEntities are needed for some things to define fully...
|
||||
|
||||
global unknownBlockIDs
|
||||
global OPTIONS, REPORTING
|
||||
#skyHighLimit=128
|
||||
#depthLimit=0
|
||||
skyHighLimit = OPTIONS['highlimit']
|
||||
if skyHighLimit > 127:
|
||||
skyHighLimit = 127
|
||||
depthLimit = OPTIONS['lowlimit']
|
||||
|
||||
#chunkLocation = 'xPos' 'zPos' ...
|
||||
chunkX = chunkLevelData['xPos'].value
|
||||
chunkZ = chunkLevelData['zPos'].value
|
||||
|
||||
CHUNKSIZE_X = 16 #static consts - global?
|
||||
CHUNKSIZE_Y = 128
|
||||
CHUNKSIZE_Z = 16
|
||||
|
||||
_Y_SHIFT = 7 # 2**7 is 128. use for fast multiply
|
||||
_YZ_SHIFT = 11 #16 * 128 is 2048, which is 2**11
|
||||
|
||||
# Blocks, Data, Skylight, ... heightmap
|
||||
#Blocks contain the block ids; Data contains the extra info: 4 bits of lighting info + 4 bits of 'extra fields'
|
||||
# eg Lamp direction, crop wetness, etc.
|
||||
# Heightmap gives us quick access to the top surface of everything - ie optimise out iterating through all sky blocks.
|
||||
|
||||
#To access a specific block from either the block or data array from XYZ coordinates, use the following formula:
|
||||
# Index = x + (y * Height + z) * Width
|
||||
|
||||
#naive starting point: LOAD ALL THE BLOCKS! :D
|
||||
|
||||
blockData = chunkLevelData['Blocks'].value #yields a TAG_Byte_Array value (bytes object)
|
||||
heightMap = chunkLevelData['HeightMap'].value
|
||||
extraData = chunkLevelData['Data'].value
|
||||
|
||||
#256 bytes of heightmap data. 16 x 16. Each byte records the lowest level
|
||||
#in each column where the light from the sky is at full strength. Speeds up
|
||||
#computing of the SkyLight. Note: This array's indexes are ordered Z,X
|
||||
#whereas the other array indexes are ordered X,Z,Y.
|
||||
|
||||
#loadedData -> we buffer everything into lists, then batch-create the
|
||||
#vertices later. This makes the model build in Blender many, many times faster
|
||||
|
||||
#list of named, distinct material meshes. add vertices to each, only in batches.
|
||||
#Optimisation: 'Hollow volumes': only add if there is at least 1 orthogonal non-same-type neighbour.
|
||||
#Aggressive optimisation: only load if there is 1 air orthogonal neighbour (or transparent materials).
|
||||
|
||||
# dataX will be dX, blender X will be bX.
|
||||
for dX in range(CHUNKSIZE_X):
|
||||
#print("looping chunk x %d" % dX)
|
||||
for dZ in range(CHUNKSIZE_Z): #-1, -1, -1):
|
||||
#get starting Y from heightmap, ignoring excess height iterations.
|
||||
#heightByte = heightMap[dX + (dZ << 4)] # z * 16
|
||||
heightByte = 127 #Fix: always start from very top... for now
|
||||
#This makes nether load properly, plus missed objects in overworld
|
||||
#omitted due to lighting calculations being wrong.
|
||||
if heightByte > skyHighLimit:
|
||||
heightByte = skyHighLimit
|
||||
#gives the LOWEST LEVEL where light is max. Start at this value, and y-- until we hit bedrock at y == 0.
|
||||
dY = heightByte
|
||||
oneBlockAbove = 0 #data value of the block 1 up from where we are now. (for neighbour comparisons)
|
||||
#for dY in range(CHUNKSIZE_Y): # naive method (iterate all)
|
||||
while dY >= depthLimit:
|
||||
|
||||
blockIndex = dY + (dZ << _Y_SHIFT) + (dX << _YZ_SHIFT) # max number of bytes in a chunk is 32768. this is coming in at 32839 for XYZ: (15,71,8)
|
||||
blockID = blockData[ blockIndex ]
|
||||
|
||||
#except IndexError:
|
||||
# print("X:%d Y:%d Z %d, blockID from before: %d, cx,cz: %d,%d. Blockindex: %d" % (dX,dY,dZ,blockID,chunkX,chunkZ, blockIndex))
|
||||
# raise IndexError
|
||||
|
||||
#create this block in the output!
|
||||
if blockID != 0 and blockID not in EXCLUDED_BLOCKS: # 0 is air
|
||||
REPORTING['blocksread'] += 1
|
||||
|
||||
#hollowness test:
|
||||
|
||||
if blockID in BLOCKDATA:
|
||||
|
||||
if ChunkReader._isExposedBlock(dX,dY,dZ, blockData, blockID, oneBlockAbove, skyHighLimit, depthLimit):
|
||||
#TODO: Make better version of this check, counting across chunks and regions.
|
||||
#Load extra data (if applicable to blockID):
|
||||
#if it has extra data, grab 4 bits from extraData
|
||||
datOffset = (int(blockIndex /2)) #divided by 2
|
||||
datHiBits = blockIndex % 2 #odd or even, will be hi or low nibble
|
||||
extraDatByte = extraData[datOffset] # should be a byte of which we only want part.
|
||||
hiMask = 0b11110000
|
||||
loMask = 0b00001111
|
||||
extraValue = None
|
||||
if datHiBits:
|
||||
#get high 4, and shift right 4.
|
||||
extraValue = loMask & (extraDatByte >> 4)
|
||||
else:
|
||||
#mask hi 4 off.
|
||||
extraValue = extraDatByte & loMask
|
||||
#create block in corresponding blockmesh
|
||||
ChunkReader.createBlock(blockID, (chunkX, chunkZ), (dX,dY,dZ), extraValue, vertexBuffer)
|
||||
else:
|
||||
REPORTING['blocksdropped'] += 1
|
||||
else:
|
||||
#print("Unrecognised Block ID: %d" % blockID)
|
||||
#createUnknownMeshBlock()
|
||||
unknownBlockIDs.add(blockID)
|
||||
dY -= 1
|
||||
oneBlockAbove = blockID # set 'last read block' to current value
|
||||
|
||||
|
||||
def createBlock(blockID, chunkPos, blockPos, extraBlockData, vertBuffer):
|
||||
"""adds a vertex to the blockmesh for blockID in the relevant location."""
|
||||
print("ChunkReader.createBlock")
|
||||
# print("blockID: " + str(blockID))
|
||||
# print("chunkPos: " + str(chunkPos))
|
||||
print("blockPos: " + str(blockPos))
|
||||
# print("extraBlockData: " + str(extraBlockData))
|
||||
# print("vertBuffer: " + str(vertBuffer))
|
||||
# print("")
|
||||
#chunkpos is X,Z; blockpos is x,y,z for block.
|
||||
# mesh = getMCBlockType(blockID, extraBlockData) #this could be inefficient. Perhaps create all the types at the start, then STOP MAKING THIS CHECK!
|
||||
# if mesh is None:
|
||||
# return
|
||||
#
|
||||
# typeName = mesh.name
|
||||
# vertex = mcToBlendCoord(chunkPos, blockPos)
|
||||
#
|
||||
# if typeName in vertBuffer:
|
||||
# vertBuffer[typeName].append(vertex)
|
||||
# else:
|
||||
# vertBuffer[typeName] = [vertex]
|
||||
|
||||
#xyz is local to the 'stone' mesh for example. but that's from 0 (world).
|
||||
#regionfile can be found from chunkPos.
|
||||
#Chunkpos is an X,Z pair.
|
||||
#Blockpos is an X,Y,Z triple - within chunk.
|
||||
|
|
@ -0,0 +1,929 @@
|
|||
# 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
|
||||
#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 mcToMTCoord(chunkPos, blockPos):
|
||||
"""Converts a Minecraft chunk X,Z pair and a Minecraft ordered X,Y,Z block
|
||||
Just remember: in Minecraft, Y points to the sky."""
|
||||
# In Minecraft, +Z (west) <--- 0 ----> -Z (east), while North is -X and South is +X
|
||||
|
||||
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 [vx,vy,vz]
|
||||
|
||||
# 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])
|
||||
|
||||
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 chunk reads 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'])
|
||||
# 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
|
||||
# ...
|
Binary file not shown.
|
@ -0,0 +1,272 @@
|
|||
|
||||
# NBT Reader module
|
||||
|
||||
from struct import calcsize, unpack, error as StructError
|
||||
|
||||
# An NBT file contains one root TAG_Compound.
|
||||
TAG_END = 0
|
||||
TAG_BYTE = 1
|
||||
TAG_SHORT = 2
|
||||
TAG_INT = 3
|
||||
TAG_LONG = 4
|
||||
TAG_FLOAT = 5
|
||||
TAG_DOUBLE = 6
|
||||
TAG_BYTE_ARRAY = 7
|
||||
TAG_STRING = 8
|
||||
TAG_LIST = 9
|
||||
TAG_COMPOUND = 10
|
||||
TAG_INT_ARRAY = 11
|
||||
|
||||
INDENTCHAR = " "
|
||||
|
||||
|
||||
#to read level.dat: compound, long, list short byte. int. ... end.
|
||||
|
||||
#Why not just do this as a 10 element array of classes, and instantiate them as list[6](bstream) ?! MAGIC!
|
||||
# that's what the py NBT guy does already!
|
||||
# See struct - for handling types and bitpacking and converting to/from bytes.
|
||||
|
||||
#pass classes around as objects. ie class Tag... we now have Tag in the namespace and can instantiate it by calling that one's __init__ method.
|
||||
|
||||
# Note that ONLY Named Tags carry the name and tagType data. Explicitly identified Tags (such as TAG_String) only contains the payload.
|
||||
|
||||
# read binary, py 3.2 etc, you get a bytes object.
|
||||
# seek(pos-in-file), tell() (number of bytes read) and read(n) read n bytes...
|
||||
|
||||
|
||||
class TagReader:
|
||||
#a class to generate tags based on ids.
|
||||
|
||||
def readNamedTag(bstream):
|
||||
"""Reads a named Tag from the bytestream provided. Returns a tuple of (name, tag) (where tag object is the payload). Name will be empty for Tag_END. """
|
||||
#print("Reading Named Tag\n")
|
||||
tbyte = bstream.read(1)[0] # read 1 byte and get its numerical value #read 1 byte, switch type generated depending (stream-reader type 'abstract?' factory
|
||||
#print("Byte read: %d" % tbyte)
|
||||
tname = TAG_String(bstream).value
|
||||
#print("Name read: %s" % tname)
|
||||
#print("RNamedT - name is %s" %tname)
|
||||
tpayload = TAGLIST[tbyte](bstream)
|
||||
tpayload.name = tname
|
||||
return (tname, tpayload)
|
||||
#object type = bleh based on the number 0-255 you just read. Which should be a 10... for TAG_Compound.
|
||||
|
||||
|
||||
def readNBT(bstream):
|
||||
rootname, rootTag = TagReader.readNamedTag(bstream)
|
||||
rootTag.name = rootname
|
||||
|
||||
#check if not at end of string and read more NBT tags if present...?
|
||||
#nfile.close()
|
||||
return rootTag
|
||||
|
||||
|
||||
##DONT PASS THE TYPE IN TO EVERY INSTANCE WHEN ITS ALWAYS THE SAME! DEFINE IT AS A CLASS VAR IN THE SUBCLASSES.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Tag:
|
||||
type = None
|
||||
|
||||
def __init__(self, bstream):
|
||||
"""Reads self-building data for this type from the bytestream given, until a complete tag instance is ready."""
|
||||
# Tag itself doesn't do this. Must be overridden.
|
||||
self.name = ""
|
||||
## named tags..? Are named tags only named when in a tag_compound that defines their names? And tag_compounds are always named?
|
||||
#self.value = "" needed?
|
||||
#payload... varies by subclass.
|
||||
self._parseContent(bstream)
|
||||
|
||||
#Needed at all?!
|
||||
def __readName(self, bstream):
|
||||
"""Only if called on a named tag .... will this be needed. may be Defined instead ... as a class method later"""
|
||||
raise NotImplementedError(self.__class__.__name__)
|
||||
pass
|
||||
|
||||
def _parseContent(self, bstream):
|
||||
raise NotImplementedError(self.__class__.__name__)
|
||||
pass # raise notimplemented...? # SUBCLASSES IMPLEMENT THIS!
|
||||
|
||||
#external code. not sure about these at all.
|
||||
#Printing / bitformatting as tree
|
||||
def toString(self):
|
||||
return self.__class__.__name__ + ('("%s")'%self.name if self.name else "") + ": " + self.__repr__() #huh... self.repr build tree
|
||||
|
||||
def printTree(self, indent=0):
|
||||
return (INDENTCHAR*indent) + self.toString()
|
||||
|
||||
|
||||
#could just skip this class....?
|
||||
class TAG_End(Tag):
|
||||
type = TAG_END
|
||||
|
||||
def _parseContent(self, bstream):
|
||||
pass
|
||||
#so, in fact... no need for this at all!?!
|
||||
|
||||
|
||||
class _TAG_Numeric(Tag):
|
||||
"""parses one of the numeric types (actual type defined by subclass)"""
|
||||
#uses struct bitformats (within each subclass) to parse the value from the data stream...
|
||||
bitformat = "" #class, not instance, var.nB: make this something that will crash badly if not overwritten properly!
|
||||
|
||||
def __init__(self, bstream):
|
||||
#if self.bitformat == "":
|
||||
# print("INCONCEIVABLE!")
|
||||
# raise NotImplementedError(self.__class__.__name__)
|
||||
#print("fmt is: %s" % self.bitformat)
|
||||
self.size = calcsize(self.bitformat)
|
||||
super(_TAG_Numeric, self).__init__(bstream)
|
||||
|
||||
def _parseContent(self, bstream):
|
||||
#struct parse it using bitformat.
|
||||
self.value = unpack(self.bitformat, bstream.read(self.size))[0] #[0] because this always returns a tuple
|
||||
|
||||
def __repr__(self):
|
||||
return "%d" % self.value
|
||||
|
||||
class TAG_Byte(_TAG_Numeric):
|
||||
bitformat = ">b" # class variable, NOT INSTANCE VARIABLE.
|
||||
#easy, it's read 1 byte!
|
||||
#def __parseContent(self, bstream):
|
||||
# self.value = bstream.read(1)[0] #grab next 1 byte in stream. That's the TAG_Byte's payload.
|
||||
# #or rather, set bitformat to ">c"
|
||||
|
||||
class TAG_Short(_TAG_Numeric):
|
||||
# type = TAG_SHORT
|
||||
bitformat = ">h"
|
||||
|
||||
class TAG_Int(_TAG_Numeric):
|
||||
bitformat = ">i"
|
||||
|
||||
class TAG_Long(_TAG_Numeric):
|
||||
# id = TAG_LONG
|
||||
bitformat = ">q"
|
||||
|
||||
class TAG_Float(_TAG_Numeric):
|
||||
# id = TAG_FLOAT
|
||||
bitformat = ">f"
|
||||
|
||||
def __repr__(self):
|
||||
return "%0.2f" % self.value
|
||||
|
||||
class TAG_Double(_TAG_Numeric):
|
||||
# id = TAG_DOUBLE
|
||||
bitformat = ">d"
|
||||
|
||||
def __repr__(self):
|
||||
return "%0.2f" % self.value
|
||||
|
||||
class TAG_Byte_Array(Tag):
|
||||
type = TAG_BYTE_ARRAY
|
||||
def _parseContent(self, bstream):
|
||||
#read the length, then grab the bytes.
|
||||
length = TAG_Int(bstream)
|
||||
self.value = bstream.read(length.value) #read n bytes from the file, where n is the numerical value of the length. Hope this works OK!
|
||||
|
||||
def __repr__(self):
|
||||
return "[%d bytes array]" % len(self.value)
|
||||
|
||||
class TAG_String(Tag):
|
||||
type = TAG_STRING
|
||||
|
||||
def _parseContent(self, bstream):
|
||||
#print ("Parsing TAG_String")
|
||||
length = TAG_Short(bstream)
|
||||
readbytes = bstream.read(length.value)
|
||||
if len(readbytes) != length.value:
|
||||
raise StructError()
|
||||
self.value = readbytes.decode('utf-8') #unicode(read, "utf-8")
|
||||
|
||||
def __repr__(self):
|
||||
return self.value
|
||||
|
||||
class TAG_List(Tag):
|
||||
type = TAG_LIST
|
||||
|
||||
def _parseContent(self, bstream):
|
||||
tagId = TAG_Byte(bstream).value
|
||||
length = TAG_Int(bstream).value
|
||||
self.value = []
|
||||
for t in range(length):
|
||||
self.value.append(TAGLIST[tagId](bstream)) #so that's just the tags, not the repeated type ids. makes sense.
|
||||
|
||||
def __repr__(self): # use repr for outputting payload values, but printTree(indent) for outputting all. Perhaps.
|
||||
if len(self.value) > 0:
|
||||
return "%d items of type %s\r\n" % (len(self.value), self.value[0].__class__.__name__) #"\r\n".join([k for k in self.value.keys()]) #to be redone!
|
||||
else:
|
||||
return "Empty List: No Items!"
|
||||
#represent self as nothing (type and name already output in printtree by the super().printTree call. Take a new line, and the rest will be output as subelements...
|
||||
|
||||
def printTree(self, indent):
|
||||
outstr = super(TAG_List, self).printTree(indent)
|
||||
for tag in self.value:
|
||||
outstr += indent*INDENTCHAR + tag.printTree(indent+1) + "\r\n"
|
||||
|
||||
return outstr
|
||||
|
||||
|
||||
class TAG_Compound(Tag):
|
||||
type = TAG_COMPOUND
|
||||
#A sequential list of Named Tags. This array keeps going until a TAG_End is found.
|
||||
#NB: "Named tags" are:
|
||||
#byte tagType
|
||||
#TAG_String name
|
||||
#[payload]
|
||||
|
||||
# This is where things get named. All names must be unique within the tag-compound. So its value is a dict.
|
||||
# it's named. so first thing is, read name.
|
||||
# then, keep on reading until you get a Tag_END
|
||||
#but, in-place create tags as you go and add them to an internal tag list...
|
||||
#essentially this parses the PAYLOAD of a named TAG_Compound...
|
||||
def _parseContent(self, bstream):
|
||||
#tagnext = readNamedTag()
|
||||
|
||||
self.value = {}
|
||||
#print("Parsing TAG_Compound!")
|
||||
readType = bstream.read(1)[0] #rly?
|
||||
#print("First compound inner tag type byte is: %d" % readType)
|
||||
while readType != TAG_END:
|
||||
tname = TAG_String(bstream).value
|
||||
#print ("Tag name read as: %s" % tname)
|
||||
payload = TAGLIST[readType](bstream)
|
||||
payload.name = tname
|
||||
self.value[tname] = payload
|
||||
readType = bstream.read(1)[0]
|
||||
|
||||
def __repr__(self): # use repr for outputting payload values, but printTree(indent) for outputting all. Perhaps.
|
||||
return "\r\n"
|
||||
#represent self as nothing (type and name already output in printtree by the super().printTree call. Take a new line, and the rest will be output as subelements...
|
||||
|
||||
def printTree(self, indent):
|
||||
outstr = super(TAG_Compound, self).printTree(indent)
|
||||
keys = self.value.keys()
|
||||
for k in keys:
|
||||
outstr += indent*INDENTCHAR + self.value[k].printTree(indent+1) + "\r\n"
|
||||
|
||||
return outstr
|
||||
|
||||
class TAG_Int_Array(Tag):
|
||||
type = TAG_INT_ARRAY
|
||||
def _parseContent(self, bstream):
|
||||
#read the length, then grab the bytes. split those out as 4-byte integers. we hope...
|
||||
tagLen = TAG_Int(bstream)
|
||||
#read out all other values as tag_ints too.
|
||||
ilength = tagLen.value
|
||||
self.value = []
|
||||
for t in range(ilength):
|
||||
self.value.append(TAG_Int(bstream).value)
|
||||
|
||||
def __repr__(self):
|
||||
#printslist = [str(i) for i in self.value]
|
||||
#prout = ', '.join(printslist)
|
||||
#return "[%d ints array] [%s]" % (len(self.value), prout)
|
||||
return "[%d ints array]" % len(self.value)
|
||||
|
||||
|
||||
TAGLIST = {TAG_BYTE: TAG_Byte, TAG_SHORT: TAG_Short, TAG_INT: TAG_Int,
|
||||
TAG_LONG:TAG_Long, TAG_FLOAT:TAG_Float, TAG_DOUBLE:TAG_Double,
|
||||
TAG_BYTE_ARRAY:TAG_Byte_Array, TAG_STRING:TAG_String,
|
||||
TAG_LIST: TAG_List, TAG_COMPOUND:TAG_Compound, TAG_INT_ARRAY: TAG_Int_Array}
|
|
@ -0,0 +1,47 @@
|
|||
# Javarandom slime Python-version test harness.
|
||||
|
||||
from . import javarandom
|
||||
|
||||
rnd = javarandom.Random
|
||||
|
||||
def isSlimeSpawn(worldSeed, xPos, zPos):
|
||||
rnd = javarandom.Random(worldSeed + jlong(xPos * xPos * 0x4c1906) + jlong(xPos * 0x5ac0db) + jlong(zPos * zPos) * 0x4307a7 + jlong(zPos * 0x5f24f) ^ 0x3ad8025f)
|
||||
return rnd.nextInt(10) == 0
|
||||
|
||||
#Totally crucial!
|
||||
def jlong(i):
|
||||
# Python and Java don't agree on how ints work.
|
||||
# Python 3 in particular treats everything as long.
|
||||
#The seed A term in the RNG was wrong, before...
|
||||
#This converts the unsigned generated int into a signed int if necessary.
|
||||
i = (i & 0xffffffff) #vital!
|
||||
|
||||
if i & (1 << 31):
|
||||
i -= (1 << 32)
|
||||
|
||||
return i
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
worldseed = 4784223057510287643 #Afarundria's seed.
|
||||
|
||||
# for z in range(64):
|
||||
# for x in range(64):
|
||||
# isSlime = isSlimeSpawn(worldseed,x,z)
|
||||
# print("[%d,%d: %d]" % (x,z,isSlime), end="\r\n")
|
||||
|
||||
|
||||
# #write out all the seeds the above line of code would generate!!
|
||||
# for z in range(64):
|
||||
# for x in range(64):
|
||||
# seeda = jlong(x * x * 0x4c1906) # BASTARD OF A 2's COMPLEMENT!
|
||||
# seedb = jlong(x * 0x5ac0db)
|
||||
# seedc = jlong(z * z) * 0x4307a7
|
||||
# seedd = jlong(z * 0x5f24f) ^ 0x3ad8025f
|
||||
#
|
||||
# seeder = (worldseed + seeda + seedb + seedc + seedd)
|
||||
# #The seed line is INCORRECT!!
|
||||
# # Here's the exact line of Java I'm trying to replicate:
|
||||
# # Random rnd = new Random(seed + (long) (xPosition * xPosition * 0x4c1906) + (long) (xPosition * 0x5ac0db) + (long) (zPosition * zPosition) * 0x4307a7L + (long) (zPosition * 0x5f24f) ^ 0x3ad8025f);
|
||||
|
||||
# print("[%d,%d: %d] {%d,%d,%d,%d}" % (x,z,seeder,seeda,seedb,seedc,seedd), end="\r\n")
|
|
@ -0,0 +1,19 @@
|
|||
import os, sys
|
||||
|
||||
#TODO: tidy this up to one location (double defined here from mineregion)
|
||||
MCPATH = ''
|
||||
if sys.platform == 'darwin':
|
||||
MCPATH = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'minecraft')
|
||||
elif sys.platform == 'linux':
|
||||
MCPATH = os.path.join(os.environ['HOME'], '.minecraft')
|
||||
else:
|
||||
MCPATH = os.path.join(os.environ['APPDATA'], '.minecraft')
|
||||
# This needs to be set by the addon during initial inclusion. Set as a bpy.props.StringProperty within the Scene, then refer to it all over this addon.
|
||||
|
||||
MCSAVEPATH = os.path.join(MCPATH, 'saves/')
|
||||
|
||||
def getMCPath():
|
||||
return MCPATH
|
||||
|
||||
def getMCSavePath():
|
||||
return MCSAVEPATH
|
Binary file not shown.
|
@ -0,0 +1,327 @@
|
|||
# Minetest MTS schematic exporter MCEdit filter
|
||||
# by sfan5
|
||||
|
||||
import zlib
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from thread import start_new_thread, allocate_lock
|
||||
|
||||
displayName = "Export to Minetest MTS schematic"
|
||||
|
||||
#Reference MC: http://hydra-media.cursecdn.com/minecraft.gamepedia.com/8/8c/DataValuesBeta.png
|
||||
#Reference MT:
|
||||
# https://github.com/minetest/minetest_game/blob/master/mods/default/nodes.lua
|
||||
# https://github.com/minetest/minetest_game/blob/master/mods/wool/init.lua
|
||||
# https://github.com/minetest/minetest_game/blob/master/mods/stairs/init.lua
|
||||
# https://github.com/minetest/minetest_game/blob/master/mods/flowers/init.lua
|
||||
conversionTable = [
|
||||
#blockid blockdata minetest-nodename
|
||||
#blockdata -1 means ignore
|
||||
#blockdata -2 means copy without change
|
||||
#blockdata -3 means copy and convert the mc facedir value to mt facedir
|
||||
#blockdata -4 is for stairs to support upside down ones
|
||||
|
||||
(1 , -1, "default:stone"),
|
||||
(2 , -1, "default:dirt_with_grass"),
|
||||
(3 , -1, "default:dirt"),
|
||||
(4 , -1, "default:cobble"),
|
||||
(5 , 3, "default:junglewood"),
|
||||
(5 , -1, "default:wood"),
|
||||
(6 , 3, "default:junglesapling"),
|
||||
(6 , -1, "default:sapling"),
|
||||
(7 , -1, "default:nyancat_rainbow"), # FIXME Bedrock
|
||||
(8 , -1, "default:water_flowing"),
|
||||
(9 , -1, "default:water_source"),
|
||||
(10 , -1, "default:lava_flowing"),
|
||||
(11 , -1, "default:lava_source"),
|
||||
(12 , -1, "default:sand"),
|
||||
(13 , -1, "default:gravel"),
|
||||
(14 , -1, "default:stone_with_gold"),
|
||||
(15 , -1, "default:stone_with_iron"),
|
||||
(16 , -1, "default:stone_with_coal"),
|
||||
(17 , 3, "default:jungletree"),
|
||||
(17 , -1, "default:tree"),
|
||||
(18 , 3, "default:jungleleaves"),
|
||||
(18 , -1, "default:leaves"),
|
||||
(20 , -1, "default:glass"),
|
||||
(21 , -1, "default:stone_with_copper"),
|
||||
(22 , -1, "default:copperblock"),
|
||||
(24 , 1, "default:sandstonebrick"),
|
||||
(24 , -1, "default:sandstone"),
|
||||
(31 , 0, "default:dry_shrub"),
|
||||
(31 , 1, "default:grass_4"),
|
||||
(31 , 2, "default:grass_3"),
|
||||
(31 , -1, "default:grass_1"),
|
||||
(32 , -1, "default:dry_shrub"),
|
||||
(35 , 0, "wool:white"),
|
||||
(35 , 1, "wool:orange"),
|
||||
(35 , 4, "wool:yellow"),
|
||||
(35 , 5, "wool:green"),
|
||||
(35 , 6, "wool:pink"),
|
||||
(35 , 7, "wool:dark_grey"),
|
||||
(35 , 8, "wool:grey"),
|
||||
(35 , 9, "wool:cyan"),
|
||||
(35 , 10, "wool:violet"),
|
||||
(35 , 11, "wool:blue"),
|
||||
(35 , 12, "wool:brown"),
|
||||
(35 , 13, "wool:dark_green"),
|
||||
(35 , 14, "wool:red"),
|
||||
(35 , 15, "wool:black"),
|
||||
(37 , -1, "flowers:dandelion_yellow"),
|
||||
(38 , 0, "flowers:rose"),
|
||||
(38 , 4, "flowers:tulip"),
|
||||
(38 , 5, "flowers:tulip"),
|
||||
(38 , 6, "flowers:tulip"),
|
||||
(38 , 7, "flowers:tulip"),
|
||||
(38 , 8, "flowers:dandelion_white"),
|
||||
(38 , -1, "flowers:geranium"), # Convert all other flowers to a geranium
|
||||
(41 , -1, "default:goldblock"),
|
||||
(42 , -1, "default:steelblock"),
|
||||
(43 , 1, "default:sandstone"),
|
||||
(43 , 2, "default:wood"),
|
||||
(43 , 3, "default:cobble"),
|
||||
(43 , 4, "default:brick"),
|
||||
(43 , 5, "default:stonebrick"),
|
||||
(44 , 0, "stairs:slab_stone"),
|
||||
(44 , 1, "stairs:slab_sandstone"),
|
||||
(44 , 2, "stairs:slab_wood"),
|
||||
(44 , 3, "stairs:slab_cobble"),
|
||||
(44 , 4, "stairs:slab_brick"),
|
||||
(44 , 5, "stairs:slab_stonebrick"),
|
||||
(44 , 8, "stairs:slab_stoneupside_down"),
|
||||
(44 , 9, "stairs:slab_sandstoneupside_down"),
|
||||
(44 , 10, "stairs:slab_woodupside_down"),
|
||||
(44 , 11, "stairs:slab_cobbleupside_down"),
|
||||
(44 , 12, "stairs:slab_brickupside_down"),
|
||||
(44 , 13, "stairs:slab_stonebrickupside_down"),
|
||||
(45 , -1, "default:brick"),
|
||||
(47 , -1, "default:bookshelf"),
|
||||
(48 , -1, "default:mossycobble"),
|
||||
(49 , -1, "default:obsidian"),
|
||||
(50 , -3, "default:torch"),
|
||||
(51 , -1, "fire:basic_flame"),
|
||||
(53 , -4, "stairs:stair_wood"),
|
||||
(54 , -1, "default:chest"),
|
||||
(56 , -1, "default:stone_with_diamond"),
|
||||
(57 , -1, "default:diamondblock"),
|
||||
(61 , -1, "default:furnace"),
|
||||
(62 , -1, "default:furnace_active"),
|
||||
(63 , -1, "default:sign_wood"),
|
||||
(64 , -1, "doors:door_wood_t_1"),
|
||||
(65 , -1, "default:ladder"),
|
||||
(66 , -1, "default:rail"),
|
||||
(67 , -4, "stairs:stair_cobble"),
|
||||
(68 , -3, "default:sign_wood"),
|
||||
(71 , -1, "doors:door_steel_t_1"),
|
||||
(78 , -1, "default:snow"),
|
||||
(79 , -1, "default:ice"),
|
||||
(80 , -1, "default:snowblock"),
|
||||
(81 , -1, "default:cactus"),
|
||||
(82 , -1, "default:clay"),
|
||||
(83 , -1, "default:papyrus"),
|
||||
(85 , -1, "default:fence_wood"),
|
||||
(98 , -1, "default:stonebrick"),
|
||||
(108, -4, "stairs:stair_brick"),
|
||||
(109, -3, "stairs:stair_stonebrick"),
|
||||
(125, 3, "default:junglewood"),
|
||||
(125, -1, "default:wood"),
|
||||
(126, 3, "stairs:slab_junglewood"),
|
||||
(126, -1, "stairs:slab_wood"),
|
||||
(128, -4, "stairs:stair_sandstone"),
|
||||
(129, -1, "default:stone_with_mese"),
|
||||
(133, -1, "default:mese"),
|
||||
(134, -4, "stairs:stair_wood"),
|
||||
(135, -4, "stairs:stair_wood"),
|
||||
(136, -4, "stairs:stair_junglewood"),
|
||||
|
||||
#Mesecons section
|
||||
# Reference: https://github.com/Jeija/minetest-mod-mesecons
|
||||
(25 , -1, "mesecons_noteblock:noteblock", "mesecons"),
|
||||
(29 , -3, "mesecons_pistons:piston_sticky_off", "mesecons"),
|
||||
(33 , -3, "mesecons_pistons:piston_normal_off", "mesecons"),
|
||||
(55 , -1, "mesecons:wire_00000000_off", "mesecons"),
|
||||
(69 , -3, "mesecons_walllever:wall_lever_off", "mesecons"),
|
||||
(70 , -1, "mesecons_pressureplates:pressure_plate_stone_off", "mesecons"),
|
||||
(72 , -1, "mesecons_pressureplates:pressure_plate_wood_off", "mesecons"),
|
||||
(73 , -1, "default:stone_with_mese", "mesecons"),
|
||||
(74 , -1, "default:stone_with_mese", "mesecons"),
|
||||
(75 , -3, "mesecons_torch:torch_off", "mesecons"),
|
||||
(76 , -3, "mesecons_torch:torch_on", "mesecons"),
|
||||
(77 , -3, "mesecons_button:button_off", "mesecons"),
|
||||
(93 , -3, "mesecons_delayer:delayer_off_1", "mesecons"),
|
||||
(94 , -3, "mesecons_delayer:delayer_on_1", "mesecons"),
|
||||
(123, -1, "mesecons_lightstone_red_off", "mesecons"),
|
||||
(124, -1, "mesecons_lightstone_red_on", "mesecons"),
|
||||
(137, -1, "mesecons_commandblock:commandblock_off", "mesecons"),
|
||||
(151, -1, "mesecons_solarpanel:solar_panel_off", "mesecons"),
|
||||
(152, -1, "default:mese", "mesecons"),
|
||||
|
||||
#Nether section
|
||||
# Reference: https://github.com/PilzAdam/nether/blob/master/init.lua
|
||||
(43 , 6, "nether:brick", "nether"),
|
||||
(87 , -1, "nether:rack", "nether"),
|
||||
(88 , -1, "nether:sand", "nether"),
|
||||
(89 , -1, "nether:glowstone", "nether"),
|
||||
(90 , -3, "nether:portal", "nether"),
|
||||
]
|
||||
|
||||
inputs = (
|
||||
("Output filename", "string"),
|
||||
("Compression level (1=fastest, 9=best)", ("7", "1", "2", "3", "4", "5", "6", "8", "9")),
|
||||
("Enabled Mods", "label"),
|
||||
("Mesecons", ("No", "Yes")),
|
||||
("Nether", ("No", "Yes")),
|
||||
)
|
||||
|
||||
numconverted = 0
|
||||
numconverted_lastsec = 0
|
||||
numconverted_lastsec_lock = allocate_lock()
|
||||
nps_thread_exit = False
|
||||
|
||||
def nps_thread():
|
||||
global numconverted, numconverted_lastsec, numconverted_lastsec_lock
|
||||
while not nps_thread_exit:
|
||||
time.sleep(1)
|
||||
numconverted_lastsec_lock.acquire()
|
||||
numconverted_lastsec = numconverted
|
||||
numconverted_lastsec_lock.release()
|
||||
|
||||
def mc2mtFacedir(blockdata):
|
||||
#Minetest
|
||||
# x+ = 2
|
||||
# x- = 3
|
||||
# z+ = 1
|
||||
# z- = 0
|
||||
#Minecraft
|
||||
# x+ = 3
|
||||
# x- = 1
|
||||
# z+ = 0
|
||||
# z- = 2
|
||||
tbl = {
|
||||
3: 2,
|
||||
1: 3,
|
||||
0: 1,
|
||||
2: 0,
|
||||
}
|
||||
return tbl.get(blockdata, 0)
|
||||
|
||||
def mc2mtstairs(tpl):
|
||||
if tpl[1] >= 4:
|
||||
return (tpl[0] + "upside_down", mc2mtFacedir(tpl[1] - 4))
|
||||
else:
|
||||
return (tpl[0], mc2mtFacedir(tpl[1]))
|
||||
|
||||
|
||||
def findConversion(blockid, blockdata, mods):
|
||||
if blockid == 0:
|
||||
return None
|
||||
for cnv in conversionTable:
|
||||
if blockid != cnv[0]:
|
||||
continue
|
||||
if len(cnv) >= 4:
|
||||
if mods.get(cnv[3], False) == False:
|
||||
continue
|
||||
if cnv[1] == -1:
|
||||
return (cnv[2], 0)
|
||||
elif cnv[1] == -2:
|
||||
return (cnv[2], blockdata)
|
||||
elif cnv[1] == -3:
|
||||
return (cnv[2], mc2mtFacedir(blockdata))
|
||||
elif cnv[1] == -4:
|
||||
return mc2mtstairs((cnv[2], blockdata))
|
||||
elif cnv[1] != blockdata:
|
||||
continue
|
||||
return (cnv[2], 0)
|
||||
return None
|
||||
|
||||
def perform(level, box, options):
|
||||
global numconverted, numconverted_lastsec, numconverted_lastsec_lock
|
||||
def getnodeid(arr, nn):
|
||||
if not nn in arr:
|
||||
arr.append(nn)
|
||||
return arr.index(nn)
|
||||
def crout():
|
||||
# zlib object, compressed data
|
||||
return [zlib.compressobj(1), ""]
|
||||
def wrout(where, what):
|
||||
where[1] += where[0].compress(what)
|
||||
def retrout(where):
|
||||
cd = where[1] + where[0].flush()
|
||||
del where[0]
|
||||
return zlib.decompress(cd)
|
||||
|
||||
try:
|
||||
f = open(options["Output filename"] + ".mts", 'w')
|
||||
except:
|
||||
raise
|
||||
|
||||
size = (box.maxx - box.minx, box.maxy - box.miny, box.maxz - box.minz)
|
||||
complvl = int(options["Compression level (1=fastest, 9=best)"])
|
||||
print("Saving file at: %s/%s.mts" % (os.getcwd(), options["Output filename"]))
|
||||
|
||||
mods = {}
|
||||
for arg in options.keys():
|
||||
if options[arg] == "Yes":
|
||||
mods[arg.lower()] = True
|
||||
|
||||
nodenames = []
|
||||
outdata1 = crout()
|
||||
outdata2 = crout()
|
||||
|
||||
numnodes = size[0] * size[1] * size[2]
|
||||
print_pc_interval = numnodes * 0.0025
|
||||
print_counter = 0
|
||||
nps_avg = 0
|
||||
start_new_thread(nps_thread, ())
|
||||
start = time.time()
|
||||
sys.stdout.write("\n")
|
||||
|
||||
for z in xrange(box.minz, box.maxz):
|
||||
for y in xrange(box.miny, box.maxy):
|
||||
for x in xrange(box.minx, box.maxx):
|
||||
c = findConversion(level.blockAt(x, y, z), level.blockDataAt(x, y, z), mods)
|
||||
if c == None:
|
||||
wrout(outdata1, struct.pack("!H", getnodeid(nodenames, "air")))
|
||||
wrout(outdata2, "\x00")
|
||||
else:
|
||||
wrout(outdata1, struct.pack("!H", getnodeid(nodenames, c[0])))
|
||||
wrout(outdata2, chr(c[1]))
|
||||
print_counter += 1
|
||||
numconverted += 1
|
||||
if print_counter >= print_pc_interval:
|
||||
numconverted_lastsec_lock.acquire()
|
||||
nps_avg = (nps_avg + (numconverted - numconverted_lastsec)) / 2
|
||||
sys.stdout.write(
|
||||
"\r%0.2f%% done, %d nodes / sec, ETA: %d sec(s) "
|
||||
% (
|
||||
(float(numconverted) / numnodes) * 100,
|
||||
nps_avg,
|
||||
float(numnodes) / nps_avg,
|
||||
)
|
||||
)
|
||||
numconverted_lastsec_lock.release()
|
||||
sys.stdout.flush()
|
||||
print_counter = 0
|
||||
|
||||
compr = zlib.compressobj(complvl)
|
||||
outdata = ""
|
||||
outdata += compr.compress(retrout(outdata1))
|
||||
outdata += compr.compress("\xff" * numnodes)
|
||||
outdata += compr.compress(retrout(outdata2))
|
||||
outdata += compr.flush()
|
||||
del compr
|
||||
end = time.time()
|
||||
sys.stdout.write("\rFinished in %0.3f seconds!" % (end-start,))
|
||||
sys.stdout.flush()
|
||||
|
||||
f.write("MTSM")
|
||||
f.write(struct.pack("!HHHH", 3, size[0], size[1], size[2]))
|
||||
for i in range(size[1]):
|
||||
f.write(chr(0xff))
|
||||
f.write(struct.pack("!H", len(nodenames)))
|
||||
for nn in nodenames:
|
||||
f.write(struct.pack("!H", len(nn)) + nn)
|
||||
f.write(outdata)
|
||||
f.close()
|
|
@ -0,0 +1,247 @@
|
|||
# Minecraft to Minetest WE schematic MCEdit filter
|
||||
# by sfan5
|
||||
|
||||
displayName = "-> Minetest WE schematic"
|
||||
|
||||
#Reference MC: http://media-mcw.cursecdn.com/8/8c/DataValuesBeta.png
|
||||
#Reference MT:
|
||||
# https://github.com/minetest/common/blob/master/mods/default/init.lua
|
||||
# https://github.com/minetest/common/blob/master/mods/wool/init.lua
|
||||
# https://github.com/minetest/common/blob/master/mods/stairs/init.lua
|
||||
conversionTable = [
|
||||
#blockid blockdata minetest-nodename
|
||||
#blockdata -1 means ignore
|
||||
#blockdata -2 means copy without change
|
||||
#blockdata -3 means copy and convert the mc facedir value to mt facedir
|
||||
#blockdata -4 is for stairs to support upside down ones
|
||||
|
||||
(1 , -1, "default:stone"),
|
||||
(2 , -1, "default:dirt_with_grass"),
|
||||
(3 , -1, "default:dirt"),
|
||||
(4 , -1, "default:cobble"),
|
||||
(5 , 3, "default:junglewood"),
|
||||
(5 , -1, "default:wood"),
|
||||
(6 , 3, "default:junglesapling"),
|
||||
(6 , -1, "default:sapling"),
|
||||
(7 , -1, "default:nyancat_rainbow"), # FIXME Bedrock
|
||||
(8 , -1, "default:water_flowing"),
|
||||
(9 , -1, "default:water_source"),
|
||||
(10 , -1, "default:lava_flowing"),
|
||||
(11 , -1, "default:lava_source"),
|
||||
(12 , -1, "default:sand"),
|
||||
(13 , -1, "default:gravel"),
|
||||
(14 , -1, "default:stone_with_gold"),
|
||||
(15 , -1, "default:stone_with_iron"),
|
||||
(16 , -1, "default:stone_with_coal"),
|
||||
(17 , 3, "default:jungletree"),
|
||||
(17 , -1, "default:tree"),
|
||||
(18 , 3, "default:jungleleaves"),
|
||||
(18 , -1, "default:leaves"),
|
||||
(20 , -1, "default:glass"),
|
||||
(21 , -1, "default:stone_with_copper"),
|
||||
(22 , -1, "default:copperblock"),
|
||||
(24 , 1, "default:sandstonebrick"),
|
||||
(24 , -1, "default:sandstone"),
|
||||
(31 , 0, "default:dry_shrub"),
|
||||
(31 , 1, "default:grass_4"),
|
||||
(31 , 2, "default:grass_3"),
|
||||
(31 , -1, "default:grass_1"),
|
||||
(32 , -1, "default:dry_shrub"),
|
||||
(35 , 0, "wool:white"),
|
||||
(35 , 1, "wool:orange"),
|
||||
(35 , 4, "wool:yellow"),
|
||||
(35 , 5, "wool:green"),
|
||||
(35 , 6, "wool:pink"),
|
||||
(35 , 7, "wool:dark_grey"),
|
||||
(35 , 8, "wool:grey"),
|
||||
(35 , 9, "wool:cyan"),
|
||||
(35 , 10, "wool:violet"),
|
||||
(35 , 11, "wool:blue"),
|
||||
(35 , 12, "wool:brown"),
|
||||
(35 , 13, "wool:dark_green"),
|
||||
(35 , 14, "wool:red"),
|
||||
(35 , 15, "wool:black"),
|
||||
(37 , -1, "flowers:dandelion_yellow"),
|
||||
(38 , -1, "flowers:rose"),
|
||||
(41 , -1, "default:goldblock"),
|
||||
(42 , -1, "default:steelblock"),
|
||||
(43 , 1, "default:sandstone"),
|
||||
(43 , 2, "default:wood"),
|
||||
(43 , 3, "default:cobble"),
|
||||
(43 , 4, "default:brick"),
|
||||
(43 , 5, "default:stonebrick"),
|
||||
(44 , 0, "stairs:slab_stone"),
|
||||
(44 , 1, "stairs:slab_sandstone"),
|
||||
(44 , 2, "stairs:slab_wood"),
|
||||
(44 , 3, "stairs:slab_cobble"),
|
||||
(44 , 4, "stairs:slab_brick"),
|
||||
(44 , 5, "stairs:slab_stonebrick"),
|
||||
(44 , 8, "stairs:slab_stoneupside_down"),
|
||||
(44 , 9, "stairs:slab_sandstoneupside_down"),
|
||||
(44 , 10, "stairs:slab_woodupside_down"),
|
||||
(44 , 11, "stairs:slab_cobbleupside_down"),
|
||||
(44 , 12, "stairs:slab_brickupside_down"),
|
||||
(44 , 13, "stairs:slab_stonebrickupside_down"),
|
||||
(45 , -1, "default:brick"),
|
||||
(47 , -1, "default:bookshelf"),
|
||||
(48 , -1, "default:mossycobble"),
|
||||
(49 , -1, "default:obsidian"),
|
||||
(50 , -3, "default:torch"),
|
||||
(51 , -1, "fire:basic_flame"),
|
||||
(53 , -4, "stairs:stair_wood"),
|
||||
(54 , -1, "default:chest"),
|
||||
(56 , -1, "default:stone_with_diamond"),
|
||||
(57 , -1, "default:diamondblock"),
|
||||
(61 , -1, "default:furnace"),
|
||||
(62 , -1, "default:furnace_active"),
|
||||
(63 , -1, "default:sign_wood"),
|
||||
(64 , -1, "doors:door_wood_t_1"),
|
||||
(65 , -1, "default:ladder"),
|
||||
(66 , -1, "default:rail"),
|
||||
(67 , -4, "stairs:stair_cobble"),
|
||||
(68 , -3, "default:sign_wood"),
|
||||
(71 , -1, "doors:door_steel_t_1"),
|
||||
(78 , -1, "default:snow"),
|
||||
(79 , -1, "default:ice"),
|
||||
(80 , -1, "default:snowblock"),
|
||||
(81 , -1, "default:cactus"),
|
||||
(82 , -1, "default:clay"),
|
||||
(83 , -1, "default:papyrus"),
|
||||
(85 , -1, "default:fence_wood"),
|
||||
(98 , -1, "default:stonebrick"),
|
||||
(108, -4, "stairs:stair_brick"),
|
||||
(109, -3, "stairs:stair_stonebrick"),
|
||||
(125, 3, "default:junglewood"),
|
||||
(125, -1, "default:wood"),
|
||||
(126, 3, "stairs:slab_junglewood"),
|
||||
(126, -1, "stairs:slab_wood"),
|
||||
(128, -4, "stairs:stair_sandstone"),
|
||||
(129, -1, "default:stone_with_mese"),
|
||||
(133, -1, "default:mese"),
|
||||
(134, -4, "stairs:stair_wood"),
|
||||
(135, -4, "stairs:stair_wood"),
|
||||
(136, -4, "stairs:stair_junglewood"),
|
||||
|
||||
#Mesecons section
|
||||
# Reference: https://github.com/Jeija/minetest-mod-mesecons/blob/master/mesecons_alias/init.lua
|
||||
(25 , -1, "mesecons_noteblock:noteblock", "mesecons"),
|
||||
(29 , -3, "mesecons_pistons:piston_sticky_off", "mesecons"),
|
||||
(33 , -3, "mesecons_pistons:piston_normal_off", "mesecons"),
|
||||
(55 , -1, "mesecons:wire_00000000_off", "mesecons"),
|
||||
(69 , -3, "mesecons_walllever:wall_lever_off", "mesecons"),
|
||||
(70 , -1, "mesecons_pressureplates:pressure_plate_stone_off", "mesecons"),
|
||||
(72 , -1, "mesecons_pressureplates:pressure_plate_wood_off", "mesecons"),
|
||||
(73 , -1, "default:stone_with_mese", "mesecons"),
|
||||
(74 , -1, "default:stone_with_mese", "mesecons"),
|
||||
(75 , -3, "mesecons_torch:torch_off", "mesecons"),
|
||||
(76 , -3, "mesecons_torch:torch_on", "mesecons"),
|
||||
(77 , -3, "mesecons_button:button_off", "mesecons"),
|
||||
(93 , -3, "mesecons_delayer:delayer_off_1", "mesecons"),
|
||||
(94 , -3, "mesecons_delayer:delayer_on_1", "mesecons"),
|
||||
(123, -1, "mesecons_lightstone_red_off", "mesecons"),
|
||||
(124, -1, "mesecons_lightstone_red_on", "mesecons"),
|
||||
(137, -1, "mesecons_commandblock:commandblock_off", "mesecons"),
|
||||
(151, -1, "mesecons_solarpanel:solar_panel_off", "mesecons"),
|
||||
(152, -1, "default:mese", "mesecons"),
|
||||
|
||||
#Nether section
|
||||
# Reference: https://github.com/PilzAdam/nether/blob/master/init.lua
|
||||
(43 , 6, "nether:brick", "nether"),
|
||||
(87 , -1, "nether:rack", "nether"),
|
||||
(88 , -1, "nether:sand", "nether"),
|
||||
(89 , -1, "nether:glowstone", "nether"),
|
||||
(90 , -3, "nether:portal", "nether"),
|
||||
|
||||
#Riesenpilz Section
|
||||
# Reference: https://github.com/HybridDog/riesenpilz/blob/master/init.lua
|
||||
(39 , -1, "riesenpilz:brown", "riesenpilz"),
|
||||
(40 , -1, "riesenpilz:red", "riesenpilz"),
|
||||
(99 , -3, "riesenpilz:head_brown", "riesenpilz"),
|
||||
(100, -3, "riesenpilz:head_brown", "riesenpilz"),
|
||||
]
|
||||
|
||||
inputs = (
|
||||
("Output filename", "string"),
|
||||
("Enabled Mods", "label"),
|
||||
("Mesecons", ("False", "True")),
|
||||
("Nether", ("False", "True")),
|
||||
("Riesenpilz", ("False", "True")),
|
||||
)
|
||||
|
||||
def mc2mtFacedir(blockdata):
|
||||
#Minetest
|
||||
# x+ = 2
|
||||
# x- = 3
|
||||
# z+ = 1
|
||||
# z- = 0
|
||||
#Minecraft
|
||||
# x+ = 3
|
||||
# x- = 1
|
||||
# z+ = 0
|
||||
# z- = 2
|
||||
tbl = {
|
||||
3: 2,
|
||||
1: 3,
|
||||
0: 1,
|
||||
2: 0,
|
||||
}
|
||||
return tbl.get(blockdata, 0)
|
||||
|
||||
def mc2mtstairs(tpl):
|
||||
if tpl[1] >= 4:
|
||||
return (tpl[0] + "upside_down", mc2mtFacedir(tpl[1] - 4))
|
||||
else:
|
||||
return (tpl[0], mc2mtFacedir(tpl[1]))
|
||||
|
||||
|
||||
def findConversion(blockid, blockdata, mods):
|
||||
if blockid == 0:
|
||||
return None
|
||||
for cnv in conversionTable:
|
||||
if blockid != cnv[0]:
|
||||
continue
|
||||
if len(cnv) >= 4:
|
||||
if mods.get(cnv[3], False) == False:
|
||||
continue
|
||||
if cnv[1] == -1:
|
||||
return (cnv[2], 0)
|
||||
elif cnv[1] == -2:
|
||||
return (cnv[2], blockdata)
|
||||
elif cnv[1] == -3:
|
||||
return (cnv[2], mc2mtFacedir(blockdata))
|
||||
elif cnv[1] == -4:
|
||||
return mc2mtstairs((cnv[2], blockdata))
|
||||
elif cnv[1] != blockdata:
|
||||
continue
|
||||
return (cnv[2], 0)
|
||||
return None
|
||||
|
||||
def perform(level, box, options):
|
||||
try:
|
||||
f = open("../" + options["Output filename"] + ".we", 'w')
|
||||
except:
|
||||
raise
|
||||
|
||||
origin = (
|
||||
box.minx + int((box.maxx - box.minx) / 2),
|
||||
box.miny + int((box.maxy - box.miny) / 2),
|
||||
box.minz + int((box.maxz - box.minz) / 2),
|
||||
)
|
||||
|
||||
mods = {}
|
||||
for arg in options.keys():
|
||||
if options[arg] == "True":
|
||||
mods[arg.lower()] = True
|
||||
|
||||
for x in xrange(box.minx, box.maxx):
|
||||
for z in xrange(box.minz, box.maxz):
|
||||
for y in xrange(box.miny, box.maxy):
|
||||
c = findConversion(level.blockAt(x, y, z), level.blockDataAt(x, y, z), mods)
|
||||
if c == None:
|
||||
continue
|
||||
calcpos = (x - origin[0], y - origin[1], z - origin[2])
|
||||
fmttpl = calcpos + (c[0], level.blockLightAt(x, y, z), c[1])
|
||||
f.write("%d %d %d %s %d %d\n" % fmttpl)
|
||||
|
||||
f.close()
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import bpy
|
||||
|
||||
def writeNoodleBuilder(material):
|
||||
"""Takes a node tree from a material, and writes out a new script that creates
|
||||
the node tree in question."""
|
||||
|
||||
nodeTree = material.node_tree
|
||||
|
||||
nodes = []
|
||||
links = []
|
||||
|
||||
for n in nodeTree.nodes:
|
||||
pos = (n.location[0], n.location[1])
|
||||
ins = [] #record default values where set.
|
||||
op = None
|
||||
|
||||
for i in n.inputs:
|
||||
if hasattr(i, 'default_value'):
|
||||
ins.append((i.name, i.default_value))
|
||||
#else don't need it.
|
||||
|
||||
if hasattr(n, "operation"):
|
||||
#it's a mathnode with operation, etc etc
|
||||
op = n.operation
|
||||
|
||||
nodes.append((n.name, n.label, pos, ins, n.type, op))
|
||||
|
||||
for l in nodeTree.links:
|
||||
linkData = (l.from_node.name, l.from_socket.name, l.to_node.name, l.to_socket.name)
|
||||
links.append(linkData)
|
||||
|
||||
#create a script to recreate this exact layout on demand.
|
||||
|
||||
t = bpy.data.texts.new(name="noodleBuilder1.py")
|
||||
t.write("import bpy\n")
|
||||
t.write("mat = bpy.data.materials['JIMMY']\nntree = mat.node_tree\n")
|
||||
t.write("ntree.nodes.clear()\n\n#Now recreate from scripted structure:\n")
|
||||
#
|
||||
for nspec in nodes:
|
||||
#line to create the node and position it:
|
||||
t.write("nn = ntree.nodes.new(type=\"{0}\")\n".format(nspec[4]))
|
||||
t.write("nn.name = \"{0}\"\n".format(nspec[0]))
|
||||
if nspec[5] is not None:
|
||||
t.write("nn.operation = '{0}'\n".format(nspec[5]))
|
||||
if nspec[1] != "":
|
||||
t.write("nn.label = \"%s\"\n" % nspec[1])
|
||||
t.write("nn.location = Vector(({:.3f}, {:.3f}))\n".format(nspec[2][0], nspec[2][1]))
|
||||
for ins in nspec[3]:
|
||||
t.write("nn.inputs['"+ins[0]+"'].default_value = "+ ins[1].__repr__() + "\n") #doesn't work for text-type values
|
||||
t.write("#link creation\n")
|
||||
t.write("nd = ntree.nodes\nlinks = ntree.links\n")
|
||||
for lspec in links:
|
||||
#it's from_node, from_socket, to_node, to_socket.
|
||||
#Creation lines look like this:
|
||||
t.write("links.new(input=nd['%s'].outputs['%s'], output=nd['%s'].inputs['%s'])\n" % lspec)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#Now use this to get the node script for material X!
|
||||
mat = bpy.data.materials['RailMat']
|
||||
|
||||
writeNoodleBuilder(mat)
|
||||
|
||||
#Check scripts! There should now be a new one.
|
Loading…
Reference in New Issue