Blender script for import minetest schematics

New member
Posts: 2
Joined: Tue Oct 08, 2019 18:49
In-game: None

Blender script for import minetest schematics

by minetesprogramercor » Mon Nov 04, 2019 21:06

First i don't speak English(i speak Spanish)
Hello i am “minetesprogramer” and this time i create this script for blender who is capable of import the default minetest schematics from a .mts file in blender,is still a very early version but work correctly in my opinion.
Note:This work is possible thanks to the code created by Gaël de Sailly.

1-You need to have installed “Blender 2.79”(I dont know if functions in “2.80”)
2-Your operative system must be Linux(only probe in Linux)
1-Open Blender.
2-Change the render mode to “Cycles Render”(“Blender Render” is the default).
3-Copy the script in the Blender text editor.
4-Run the script(mouse over the blender text editor and (Alt+P) or in the Blender text editor:text>Run Script )
5-Open the new Minetest Panel.
6-Press the Import button.
7-Select the file from you want to import the schematics(and activate this)
Code: Select all
The Script(V0.2):
###Nota:El codigo que crea la clase Schem,fue creado por Gaël de Sailly.
####Note:The code for the Schem class was created by Gaël de Sailly.

# Python library to handle Minetest schematics, by Gaël de Sailly, Nov 10 2018.
# Inspired by python-minetest library by LeMagnesium:
# Using NumPy for speed and memory efficiency (more adapted to huge schematics)

import numpy as np
import zlib
from io import BytesIO

bulk_dtype = np.dtype([("node", ">u2"), ("prob", "u1"), ("force", "?"), ("param2", "u1")])

class Schem:
   def __init__(self, *args, **kwargs):
      if len(args) >= 1:
         if isinstance(args[0], str):
            if isinstance(args[0], tuple):
               shape = args
               shape = tuple(args[:3])
            self.version = 4
            self.yprobs = np.zeros(shape[1], dtype="u1")
            self.nodes = ["air"]
   = np.zeros(shape, dtype=bulk_dtype)

      if 'version' in kwargs:
         self.version = kwargs['version']
      if 'yprobs' in kwargs:
         self.yprobs = kwargs['yprobs']
      if 'nodes' in kwargs:
         self.nodes = kwargs['nodes']
      if 'data' in kwargs: = kwargs['data']

   def load(self, filename):
      f = open(filename, "rb")

      if != b"MTSM":
         print("WARNING: Signature 'MTSM' not recognized!")

      self.version = np.fromstring(, dtype=">u2")[0]
      size = tuple(np.fromstring(, dtype=">u2"))
      volume = int(size[0])*int(size[1])*int(size[2])
      rev_size = tuple(reversed(size)) # Reversed shape tuple, to handle the schematic file in which data are stored as Z[Y[X]]
      self.yprobs = np.fromstring([1]), dtype="u1")

      nodecount = np.fromstring(, dtype=">u2")[0]
      self.nodes = []
      for node in range(nodecount):
         namelength = np.fromstring(, dtype=">u2")[0]
      bulk = BytesIO(zlib.decompress(
      f.close() # We have read all

      data = np.zeros(rev_size, dtype=bulk_dtype)

      data["node"] = np.fromstring(*2), dtype=">u2").reshape(rev_size)
      #data["force"], data["prob"] = np.divmod(np.fromstring(, dtype="u1").reshape(rev_size), 128)
      data["param2"] = np.fromstring(, dtype="u1").reshape(rev_size) = data.swapaxes(0, 2) # data axis order is Z[Y[X]], we want X[Y[Z]]

   def save(self, filename, compression=9):

      f = open(filename, "wb")


      size =
      f.write(np.array(size, dtype=">u2").tobytes())

      f.write(np.resize(self.yprobs, size[1]).tobytes()) # the yprobs list's size must be equal to the vertical size of the schematic ; if not, resize it.

      nodecount = len(self.nodes)
      for node in self.nodes:
         namelength = len(node)

      data =, 2) # get MTS's axes order again

      bulk = BytesIO()

      bulk.write((data["force"] * 128 + data["prob"]).astype("u1").tobytes())

      f.write(zlib.compress(bulk.getbuffer(), compression))

   def cleanup_nodelist(self):
      existing_nodes = np.unique(["node"])

      new_nodelist = [self.nodes[i] for i in existing_nodes]
      transform_list = np.zeros(len(self.nodes), dtype=">u2")
      duplicates = 0
      update_array = False
      for new_i, old_i in enumerate(existing_nodes):
         i = new_i - duplicates
         if new_nodelist[i] in new_nodelist[:i]: # If this node is a duplicate
            transform_list[old_i] = new_nodelist.index(new_nodelist[i])
            duplicates += 1 # Keep count of removed duplicates to offset
         transform_list[old_i] = i
         if old_i != i:
            update_array = True

      self.nodes = new_nodelist
      if update_array:["node"] = transform_list[["node"]]

   def __getitem__(self, slices):
      data =[slices].copy()
      nodes = self.nodes[:]
      if isinstance(slices, tuple) and len(slices) >= 2:
         yprobs = self.yprobs[slices[1]].copy()
         yprobs = self.yprobs.copy()

      return Schem(data=data, nodes=nodes, yprobs=yprobs, version=self.version)

import bpy

bpy.context.scene.render.engine = "CYCLES"

def importschem(context, filepath):
    s = Schem(filepath)

    #Esta funcion crea una lista con todos los nodos.
    #This function create a list whit all nodes from the schem.
    def lnode_pos(schem = s):
        len =
        i1,i2,i3 = 0,0,0
        lista = []
        while i1 < len:
            l2 =[i1]
            ll2 = l2.__len__()
            while i2 <ll2:
                l3 =[i1][i2]
                ll3 = l3.__len__()
                while i3 <ll3:
                    element =[i1,i2,i3]
                    node = schem.nodes[element["node"]]
                    tnode = str(node)+"/"+str(i1)+"/"+str(i2)+"/"+str(i3)
                    i3 = i3 + 1
                i2 = i2 +1
                i3 = 0
            i1 = i1 + 1
            i2 = 0
        return lista

    #Crea la schematica.
    #Create the schem.
    def imp():
        for n in lnode_pos():
            d = n.split("/")
            obj = d[0]
            if obj != "air":
                #Si existe el nodo(grupo) en la escena,lo utiliza para crear la "schem".
                #If the node exist(group) in the scene,is used for create the schem.
                    bpy.ops.object.group_instance_add(group=obj, view_align=False, location=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
                #De no existir,crea un cubo.
                #If not exist,create a cube.
                bpy.context.object.location[0] = int(d[1])
                bpy.context.object.location[1] = int(d[3])
                bpy.context.object.location[2] = int(d[2])
    return {'FINISHED'}

bl_info = {#Metadato que contiene informacion sobre el script o add-on.
    "name": "Import Schemmatics",
    "author": "Lazaro",
    "version": (0, 2),
    "location": "View3D > Properties > Minetest",
    "description": "A buton to 'Minetest Panel' who inmports Schemmatics of Minetest from a .mts file ",
    "category": "Minetest"}

from bpy_extras.io_utils import ImportHelper
class ImportMinetestSchemsfromfile(bpy.types.Operator,ImportHelper):
    """My Object Moving Script"""     
    bl_idname = "minetest.importschems"       
    bl_label = "Minetest:Import Nodes"         
    bl_options = {'REGISTER', 'UNDO'}
    from bpy.props import StringProperty, BoolProperty, EnumProperty
    filename_ext = ".mts"
    filter_glob = StringProperty(
            maxlen=255,  # Max internal buffer length, longer would be clamped.

    def execute(self, context):        # execute() is called when running the operator.
        return {'FINISHED'}

class MinetestImportShemsPanel(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_context = "objectmode"
    bl_label = "Import Shems"
    bl_region_type = "TOOLS"
    bl_category = "Minetest"   
    def draw(self,context):
        layout = self.layout
        row = layout.row()
        row.scale_y = 1.0
        row.operator("minetest.importschems",text = "Import")

# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
    self.layout.operator(ImportMinetestSchemsfromfile.bl_idname, text="Text Import Operator")

def register():
    print("The Minetest Panel was created")
def unregister():
    print("Closing the Minetest Panel")
if __name__ == "__main__":#Esto permite correr el script desde el editor de texto de Blender.

User avatar
Posts: 897
Joined: Sat Oct 27, 2018 08:32
GitHub: runsy

Return to Modding Discussion

Who is online

Users browsing this forum: No registered users and 2 guests