No dejarán nunca de sorprenderme. Y eso que al principio me costó convencerles de que trabajar en equipo era fundamental para conseguir el objetivo final: construir un castillo de manera colaborativa en un mundo virtual para luego imprimirlo en 3D! Juntar a una veintena de muchachos de Primaria en un mismo escenario con acceso ilimitado a fuentes de lava, torrentes de agua, fuego y dinamita y pretender que no lo utilicen unos contra otros, sin gritos, de forma «ordenada»… es una prueba digna de Hércules. Yo no la superé a la primera, ni a la segunda,…
Pero bastó enseñarles una diminuta maqueta impresa hecha con el Minetest para que cambiaran y se pusieran «en serio». Os adjunto un pequeño tutorial para replicar la «experiencia» y unas fotos con los resultados.
¡Artículo Actualizado!: https://diocesanos.es/blogs/equipotic/2019/02/12/nueva-version-del-modulo-para-imprimir-en-3d-modelos-de-minetest/
Minetest
Puedes descargarlo de forma gratuita desde la web original del programa:
http://www.minetest.net/downloads/
Hay versiones para Windows, Mac, Android, FreeBSD y Linux. El tutorial se centra en este último, y en concreto en el “sabor” Ubuntu disponible en los colegios.
Instalación e inicio primera vez
Abre un terminal y ejecuta las siguientes órdenes:
sudo apt-get update sudo apt-get install minetest openscad slic3r
Ejecútalo, bien desde el menú “Aplicaciones” → “Juegos” o utilizando las teclas “ALT + F2” en caso de no estar disponible ese menú:
Ajusta las opciones para un equipo con bajos recursos. Ve a la solapa “Configuración” y desactiva todas las opciones tal como se muestra en la figura, en especial “Iluminación suave”, “Habilitar partículas”, “Nubes 3D” y “Sombreadores”. Las opciones de “Texturizado” también al mínimo.
Cierra el programa a continuación.
Creación del “mod“ de exportación
Minetest no viene preparado para generar el fichero de datos en el formato que necesitamos. Pero sí tiene un mecanismo bien documentado para la creación de complementos y modificaciones (mods) para dotarle de nuevas características. Para más información consulta los siguientes enlaces:
Opción 1: Como módulo local para el usuario del equipo
Creamos la siguiente estructura de archivos y carpetas (con el usuario “alumno”):
mkdir -p /home/alumno/.minetest/mods
mkdir -p /home/alumno/.minetest/mods/openscad
mkdir -p /home/alumno/.minetest/mods/openscad/textures
touch /home/alumno/.minetest/mods/openscad/depends.txt
touch /home/alumno/.minetest/mods/openscad/init.lua
mods/ └── openscad/ ├── depends.txt ├── init.lua └── textures/
Opción 2: Como módulo para un servidor dedicado
Creamos la siguiente estructura de archivos y carpetas (con el usuario “root” o el dedicado al server):
mkdir -p /var/games/minetest-server/.minetest/mods
mkdir -p /var/games/minetest-server/.minetest/mods/openscad
mkdir -p /var/games/minetest-server/.minetest/mods/openscad/textures
touch /var/games/minetest-server/.minetest/mods/openscad/depends.txt
touch /var/games/minetest-server/.minetest/mods/openscad/init.lua
mods/ └── openscad/ ├── depends.txt ├── init.lua └── textures/
Programa de exportación
Edita ahora el fichero “init.lua” y copia el siguiente código:
-- Módulo de exportación a OpenSCAD para impresión 3D -- Copyright(C)2017 - David Martín Pascual -- Versión v1.2 -- Proceso de exportación exportanodos = { bloquesignorar = { ['air'] = 1, ['ignore'] = 1, ['unknown'] = 1 }, -- Lista de tipos de bloque que se tendrán en cuenta para el modelo a exportar bloquesadmitidos = { -- Bloques sólidos 1x1x1 ['default:acacia_wood'] = 1, ['default:aspen_wood'] = 1, ['default:brick'] = 1, ['default:bronzeblock'] = 1, ['default:clay'] = 1, ['default:coalblock'] = 1, ['default:cobble'] = 1, ['default:copperblock'] = 1, ['default:desert_cobble'] = 1, ['default:desert_sand'] = 1, ['default:desert_stone'] = 1, ['default:desert_stone_block'] = 1, ['default:desert_stonebrick'] = 1, ['default:diamondblock'] = 1, ['default:dirt'] = 1, ['default:dirt_with_dry_grass'] = 1, ['default:dirt_with_grass'] = 1, ['default:dirt_with_snow'] = 1, ['default:goldblock'] = 1, ['default:gravel'] = 1, ['default:ice'] = 1, ['default:junglewood'] = 1, ['default:mese'] = 1, ['default:meselamp'] = 1, ['default:mossycobble'] = 1, ['default:obsidian_block'] = 1, ['default:obsidianbrick'] = 1, ['default:pine_wood'] = 1, ['default:sand'] = 1, ['default:sandstone'] = 1, ['default:sandstone_block'] = 1, ['default:sandstonebrick'] = 1, ['default:silver_sand'] = 1, ['default:snowblock'] = 1, ['default:steelblock'] = 1, ['default:stone'] = 1, ['default:stone_block'] = 1, ['default:stone_with_coal'] = 1, ['default:stone_with_copper'] = 1, ['default:stone_with_diamond'] = 1, ['default:stone_with_gold'] = 1, ['default:stone_with_iron'] = 1, ['default:stone_with_mese'] = 1, ['default:stonebrick'] = 1, ['default:wood'] = 1, ['wool:white'] = 1, ['wool:grey'] = 1, ['wool:dark_grey'] = 1, ['wool:black'] = 1, ['wool:blue'] = 1, ['wool:cyan'] = 1, ['wool:green'] = 1, ['wool:dark_green'] = 1, ['wool:yellow'] = 1, ['wool:orange'] = 1, ['wool:brown'] = 1, ['wool:red'] = 1, ['wool:pink'] = 1, ['wool:magenta'] = 1, ['wool:violet'] = 1, -- Muros delgados ['walls:cobble'] = 2, ['walls:mossycobble'] = 2, ['walls:desertcobble'] = 2, -- Puertas (dos alturas) ['doors:door_steel_a'] = 3, ['doors:door_steel_b'] = 3, ['doors:door_steel_b_1'] = 3, ['doors:door_steel_b_2'] = 3, ['doors:door_wood_a'] = 3, ['doors:door_wood_b'] = 3, ['doors:door_wood_b_1'] = 3, ['doors:door_wood_b_2'] = 3, -- Cerramientos ['default:fence_wood'] = 4, ['default:fence_junglewood'] = 4, ['default:fence_pine_wood'] = 4, ['default:fence_acacia_wood'] = 4, ['default:fence_aspen_wood'] = 4, -- Puertas cerramientos ['doors:gate_wood_open'] = 5, ['doors:gate_wood_closed'] = 5, ['doors:gate_junglewood_open'] = 5, ['doors:gate_junglewood_closed'] = 5, ['doors:gate_acacia_wood_open'] = 5, ['doors:gate_acacia_wood_closed'] = 5, ['doors:gate_aspen_wood_open'] = 5, ['doors:gate_aspen_wood_closed'] = 5, ['doors:gate_pine_wood_open'] = 5, ['doors:gate_pine_wood_closed'] = 5, -- Verjas ['xpanes:bar'] = 6, ['xpanes:bar_flat'] = 6, -- Escaleras ['default:ladder_steel'] = 7, ['default:ladder_wood'] = 7, -- Trampillas ['doors:trapdoor_steel'] = 8, ['doors:trapdoor_steel_open'] = 8, ['doors:trapdoor'] = 8, ['doors:trapdoor_open'] = 8, -- Antorcha ['default:torch'] = 9, ['default:torch_wall'] = 9 -- Losas "stairs:slab_*" y escalones "stairs:stair_*" en el código }, -- Exporta un trozo de mapa comenzando en el bloque (ax1, ay1, az1) y terminando en el (ax2, ay2, az2) exportabloques = function(athis, anombrefichero, ax1, ay1, az1, ax2, ay2, az2) -- Escritura del mapa en formato openscad local ruta = minetest.get_worldpath() .. "/" .. anombrefichero; local destino, emsg = io.open(ruta, "w"); if not destino then error(emsg); end -- Cabecera y módulos del fichero .scad destino:write(string.format('// Mapa de [%d, %d, %d] a [%d, %d, %d]\n\n', ax1, ay1, az1, ax2, ay2, az2)); destino:write('module bloque(x,y,z,lon) { translate([x, y, z]) cube([1.001, lon + .001, 1.001]); }\n\n'); destino:write('module losa(x,y,z,p2) {\n'); destino:write(' if ((p2==0)||(p2==1)||(p2==2)||(p2==3)) translate([x, y, z]) cube([1.001, 1.001, 0.501]);\n'); destino:write(' if ((p2==4)||(p2==5)||(p2==6)||(p2==7)) translate([x, y, z]) cube([1.001, 0.501, 1.001]);\n'); destino:write(' if ((p2==8)||(p2==9)||(p2==10)||(p2==11)) translate([x, y+0.5, z]) cube([1.001, 0.501, 1.001]);\n'); destino:write(' if ((p2==12)||(p2==13)||(p2==14)||(p2==15)) translate([x, y, z]) cube([0.501, 1.001, 1.001]);\n'); destino:write(' if ((p2==16)||(p2==17)||(p2==18)||(p2==19)) translate([x+0.5, y, z]) cube([0.501, 1.001, 1.001]);\n'); destino:write(' if ((p2==20)||(p2==21)||(p2==22)||(p2==23)) translate([x, y, z+0.5]) cube([1.001, 1.001, 0.501]); }\n\n'); destino:write('module escalon(x,y,z,p2) {\n'); destino:write(' losa(x,y,z,p2);\n'); destino:write(' if ((p2==0)||(p2==6)) translate([x, y+0.5, z+0.5]) cube([1.001, 0.501, 0.501]);\n'); destino:write(' if ((p2==1)||(p2==15)) translate([x+0.5, y, z+0.5]) cube([0.501, 1.001, 0.501]);\n'); destino:write(' if ((p2==2)||(p2==8)) translate([x, y, z+0.5]) cube([1.001, 0.501, 0.501]);\n'); destino:write(' if ((p2==3)||(p2==17)) translate([x, y, z+0.5]) cube([0.501, 1.001, 0.501]);\n'); destino:write(' if ((p2==16)||(p2==7)) translate([x, y+0.5, z]) cube([0.501, 0.501, 1.001]);\n'); destino:write(' if ((p2==18)||(p2==11)) translate([x, y, z]) cube([0.501, 0.501, 1.001]);\n'); destino:write(' if ((p2==9)||(p2==14)) translate([x+0.5, y, z]) cube([0.501, 0.501, 1.001]);\n'); destino:write(' if ((p2==5)||(p2==12)) translate([x+0.5, y+0.5, z]) cube([0.501, 0.501, 1.001]);\n'); destino:write(' if ((p2==20)||(p2==4)) translate([x, y+0.5, z]) cube([1.001, 0.501, 0.501]);\n'); destino:write(' if ((p2==21)||(p2==19)) translate([x, y, z]) cube([0.501, 1.001, 0.501]);\n'); destino:write(' if ((p2==22)||(p2==10)) translate([x, y, z]) cube([1.001, 0.501, 0.501]);\n'); destino:write(' if ((p2==23)||(p2==13)) translate([x+0.5, y, z]) cube([0.501, 1.001, 0.501]); }\n\n'); destino:write('module muro(x,y,z,con) {\n'); destino:write(' translate([x+0.333, y+0.333, z]) cube([0.334, 0.334, 1.001]);\n'); destino:write(' if ((con==1)||(con==3)||(con==5)||(con==7)||(con==9)||(con==11)||(con==13)||(con==15)) translate([x+0.375, y+0.666, z]) cube([0.251, 0.334, 0.66]);\n'); destino:write(' if ((con==2)||(con==3)||(con==6)||(con==7)||(con==10)||(con==11)||(con==14)||(con==15)) translate([x+0.666, y+0.375, z]) cube([0.334, 0.251, 0.66]);\n'); destino:write(' if ((con==4)||(con==5)||(con==6)||(con==7)||(con==12)||(con==13)||(con==14)||(con==15)) translate([x+0.375, y, z]) cube([0.251, 0.334, 0.66]);\n'); destino:write(' if ((con==8)||(con==9)||(con==10)||(con==11)||(con==12)||(con==13)||(con==14)||(con==15)) translate([x, y+0.375, z]) cube([0.334, 0.251, 0.66]);}\n\n'); destino:write('module valla(x,y,z,con) {\n'); destino:write(' translate([x+0.375, y+0.375, z]) cube([0.251, 0.251, 1.001]);\n'); destino:write(' if ((con==1)||(con==3)||(con==5)||(con==7)||(con==9)||(con==11)||(con==13)||(con==15)) {\n'); destino:write(' translate([x+0.42, y+0.625, z+0.2]) cube([0.16, 0.376, 0.16]);\n'); destino:write(' translate([x+0.42, y+0.625, z+0.6]) cube([0.16, 0.376, 0.16]);}\n'); destino:write(' if ((con==2)||(con==3)||(con==6)||(con==7)||(con==10)||(con==11)||(con==14)||(con==15)) {\n'); destino:write(' translate([x+0.625, y+0.42, z+0.2]) cube([0.376, 0.16, 0.16]);\n'); destino:write(' translate([x+0.625, y+0.42, z+0.6]) cube([0.376, 0.16, 0.16]);}\n'); destino:write(' if ((con==4)||(con==5)||(con==6)||(con==7)||(con==12)||(con==13)||(con==14)||(con==15)) {\n'); destino:write(' translate([x+0.42, y, z+0.2]) cube([0.16, 0.376, 0.16]);\n'); destino:write(' translate([x+0.42, y, z+0.6]) cube([0.16, 0.376, 0.16]);}\n'); destino:write(' if ((con==8)||(con==9)||(con==10)||(con==11)||(con==12)||(con==13)||(con==14)||(con==15)) {\n'); destino:write(' translate([x, y+0.42, z+0.2]) cube([0.376, 0.16, 0.16]);\n'); destino:write(' translate([x, y+0.42, z+0.6]) cube([0.376, 0.16, 0.16]);}}\n\n'); destino:write('module escalerabasica() { union() {\n'); destino:write(' translate([0.4, -0.4, 0]) cylinder(r=0.1, h=1.01);\n'); destino:write(' translate([0.4, 0.4, 0]) cylinder(r=0.1, h=1.01);\n'); destino:write(' translate([0.4, 0.5, 0.2]) rotate([90, 0, 0]) cylinder(r=0.05, h=1.01);\n'); destino:write(' translate([0.4, 0.5, 0.45]) rotate([90, 0, 0]) cylinder(r=0.05, h=1.01);\n'); destino:write(' translate([0.4, 0.5, 0.70]) rotate([90, 0, 0]) cylinder(r=0.05, h=1.01);\n'); destino:write(' translate([0.4, 0.5, 0.95]) rotate([90, 0, 0]) cylinder(r=0.05, h=1.01); } }\n\n'); destino:write('module escalera(x,y,z,p2) { $fn=8;\n'); destino:write(' if (p2 == 2) translate([x+0.5, y+0.5, z]) rotate([0, 0, 0]) escalerabasica();\n'); destino:write(' if (p2 == 3) translate([x+0.5, y+0.5, z]) rotate([0, 0, 180]) escalerabasica();\n'); destino:write(' if (p2 == 4) translate([x+0.5, y+0.5, z]) rotate([0, 0, 90]) escalerabasica();\n'); destino:write(' if (p2 == 5) translate([x+0.5, y+0.5, z]) rotate([0, 0, 270]) escalerabasica(); }\n\n'); destino:write('module trampabasica(a,b) { rotate([b, 0, a]) translate([-0.5, -0.5, -0.5]) difference() {\n'); destino:write(' cube([1.001, 0.1, 1.001]);\n'); destino:write(' translate([0.1, -0.1, 0.1]) cube([0.8, 0.12, 0.8]);\n'); destino:write(' translate([0.1, 0.08, 0.1]) cube([0.8, 0.1, 0.8]);\n'); destino:write(' translate([0.2, -0.1, 0.2]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.2, -0.1, 0.6]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.6, -0.1, 0.2]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.6, -0.1, 0.6]) cube([0.2, 0.2, 0.3]);} }\n\n'); destino:write('module trampa(x,y,z,p2) { translate([x+0.5, y+0.5, z+0.5]) {\n'); destino:write(' if (p2 == -1) trampabasica(0, 90);\n'); destino:write(' if (p2 == 0) trampabasica(180, 0);\n'); destino:write(' if (p2 == 1) trampabasica(90, 0);\n'); destino:write(' if (p2 == 2) trampabasica(0, 0);\n'); destino:write(' if (p2 == 3) translate([x+0.1, y, z]) rotate([0, -90, 0]) trampabasica(); } }\n\n'); destino:write('module antorchabasica(a,b) { $fn=8; union() {\n'); destino:write(' rotate([a, b, 0]) cylinder(r=0.08, h=0.6);\n'); destino:write(' sphere(0.15);\n'); destino:write(' cylinder(r1=0.125, r2=0, h=0.35);} }\n\n'); destino:write('module antorcha(x,y,z,p2) {\n'); destino:write(' if (p2 == 1) translate([x+0.5, y+0.5, z+0.6]) antorchabasica(0,180);\n'); destino:write(' if (p2 == 2) translate([x+0.7, y+0.5, z+0.6]) antorchabasica(0,145);\n'); destino:write(' if (p2 == 3) translate([x+0.3, y+0.5, z+0.6]) antorchabasica(0,-145);\n'); destino:write(' if (p2 == 4) translate([x+0.5, y+0.7, z+0.6]) antorchabasica(-145,0);\n'); destino:write(' if (p2 == 5) translate([x+0.5, y+0.3, z+0.6]) antorchabasica(145,0); }\n\n'); destino:write('module puertabasica(a) { rotate([0, 0, a]) translate([-0.5, -0.4, 0]) difference() {\n'); destino:write(' cube([1.001, 0.1, 2.001]);\n'); destino:write(' translate([0.1, -0.1, 0.1]) cube([0.8, 0.12, 0.8]);\n'); destino:write(' translate([0.1, 0.08, 0.1]) cube([0.8, 0.1, 0.8]);\n'); destino:write(' translate([0.1, -0.1, 1.1]) cube([0.8, 0.12, 0.8]);\n'); destino:write(' translate([0.1, 0.08, 1.1]) cube([0.8, 0.1, 0.8]);\n'); destino:write(' translate([0.2, -0.1, 1.2]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.2, -0.1, 1.6]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.6, -0.1, 1.2]) cube([0.2, 0.2, 0.3]);\n'); destino:write(' translate([0.6, -0.1, 1.6]) cube([0.2, 0.2, 0.3]); } }\n\n'); destino:write('module puerta(x,y,z,p2) { translate([x+0.5, y+0.5, z]) {\n'); destino:write(' if (p2 == 0) puertabasica(0);\n'); destino:write(' if (p2 == 1) puertabasica(-90);\n'); destino:write(' if (p2 == 2) puertabasica(180);\n'); destino:write(' if (p2 == 3) puertabasica(90); } }\n\n'); destino:write('module portonbasico(a,b) { rotate([0, 0, a]) {\n'); destino:write(' translate([-0.625, -0.125, 0]) cube([0.251, 0.251, 1.001]);\n'); destino:write(' translate([0.375, -0.125, 0]) cube([0.251, 0.251, 1.001]);\n'); destino:write(' translate([-0.5, -0.08, 0.6]) rotate([0, 0, b == 0 ? -90 : 0]) cube([1, 0.16, 0.16]);\n'); destino:write(' translate([-0.5, -0.08, 0.1]) rotate([0, -30 ,b == 0 ? -90 : 0]) cube([1, 0.16, 0.16]); } }\n\n'); destino:write('module porton(x,y,z,p2,a) { translate([x+0.5, y+0.5, z]) {\n'); destino:write(' if (p2 == 0) portonbasico(0,a);\n'); destino:write(' if (p2 == 1) portonbasico(-90,a);\n'); destino:write(' if (p2 == 2) portonbasico(180,a);\n'); destino:write(' if (p2 == 3) portonbasico(90,a); } }\n\n'); destino:write('module verjabasico(r) {rotate([0,0,r]) translate([-0.05, -0.05, 0]) difference() {\n'); destino:write(' cube([0.551, 0.1, 1.001]);\n'); destino:write(' translate([0.05, -0.05, 0.05]) cube([0.18, 0.2, 0.801]);\n'); destino:write(' translate([0.35, -0.05, 0.05]) cube([0.18, 0.2, 0.801]); } }\n\n'); destino:write('module verja(x,y,z,con) { translate([x+0.5, y+0.5, z]) {\n'); destino:write(' if ((con==1)||(con==3)||(con==5)||(con==7)||(con==9)||(con==11)||(con==13)||(con==15)) verjabasico(90);\n'); destino:write(' if ((con==2)||(con==3)||(con==6)||(con==7)||(con==10)||(con==11)||(con==14)||(con==15)) verjabasico(0);\n'); destino:write(' if ((con==4)||(con==5)||(con==6)||(con==7)||(con==12)||(con==13)||(con==14)||(con==15)) verjabasico(-90);\n'); destino:write(' if ((con==8)||(con==9)||(con==10)||(con==11)||(con==12)||(con==13)||(con==14)||(con==15)) verjabasico(180); } }\n\n'); destino:write('union() {\n'); -- Manipuladores de nodos contenidos entre las coordenadas indicadas local vm = minetest.get_voxel_manip(); local pMin, pMax = vm:read_from_map({ x = ax1, y = ay1, z = az1 }, { x = ax2, y = ay2, z = az2 }); local data = vm:get_data(); local par2 = vm:get_param2_data(); local va = VoxelArea:new({ MinEdge = pMin, MaxEdge = pMax }); -- Comprueba conexión de muros local function muro_vecino(x, y, z) if va:contains(x, y, z) then local ind = va:index(x, y, z); local nid = data[ind]; local nom = minetest.get_name_from_content_id(nid); local val = athis.bloquesadmitidos[nom]; if (val == 2) or (string.find(nom, 'stone') ~= nil) or (string.find(nom, 'cobble') ~= nil) then return true; end end return false; end -- Comprueba conexión de vallas local function valla_vecina(x, y, z) if va:contains(x, y, z) then local ind = va:index(x, y, z); local nid = data[ind]; local nom = minetest.get_name_from_content_id(nid); local val = athis.bloquesadmitidos[nom]; if (val == 4) or (val == 5) then return true; end end return false; end -- Comprueba conexión de verjas local function verja_vecina(x, y, z) if va:contains(x, y, z) then local ind = va:index(x, y, z); local nid = data[ind]; local nom = minetest.get_name_from_content_id(nid); local val = athis.bloquesadmitidos[nom]; if (val == 6) then return true; end end return false; end -- Exploración de bloques a exportar for ly = ay1, ay2 do local oz = ly - ay1; for lx = ax1, ax2 do local ox = lx - ax1; local longitud = 0; local inicio = 0; for lz = az1, az2 do local oy = lz - az1; local indice = va:index(lx, ly, lz); local nodo_id = data[indice]; local nodo_nom = minetest.get_name_from_content_id(nodo_id); local nodo_p2 = par2[indice]; local valor = athis.bloquesadmitidos[nodo_nom]; -- Agrupar bloques para simplificar modelo if valor == 1 then if longitud == 0 then inicio = oy; end longitud = longitud + 1; else if longitud > 0 then destino:write(string.format(' bloque(%d, %d, %d, %d);\n', ox, inicio, oz, longitud)); longitud = 0; end local vecinos = 0; local subn = string.sub(nodo_nom, 8, 12); if subn == 'slab_' then destino:write(string.format(' losa(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif subn == 'stair' then destino:write(string.format(' escalon(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif valor == 2 then if muro_vecino(lx, ly, lz+1) then vecinos = vecinos + 1; end if muro_vecino(lx+1, ly, lz) then vecinos = vecinos + 2; end if muro_vecino(lx, ly, lz-1) then vecinos = vecinos + 4; end if muro_vecino(lx-1, ly, lz) then vecinos = vecinos + 8; end destino:write(string.format(' muro(%d, %d, %d, %d);\n', ox, oy, oz, vecinos)); elseif valor == 3 then destino:write(string.format(' puerta(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif valor == 4 then if valla_vecina(lx, ly, lz+1) then vecinos = vecinos + 1; end if valla_vecina(lx+1, ly, lz) then vecinos = vecinos + 2; end if valla_vecina(lx, ly, lz-1) then vecinos = vecinos + 4; end if valla_vecina(lx-1, ly, lz) then vecinos = vecinos + 8; end destino:write(string.format(' valla(%d, %d, %d, %d);\n', ox, oy, oz, vecinos)); elseif valor == 5 then local cerrado = 1 if (string.find(nodo_nom, '_closed') == nil) then cerrado = 0; end destino:write(string.format(' porton(%d, %d, %d, %d, %d);\n', ox, oy, oz, nodo_p2, cerrado)); elseif valor == 6 then if (string.find(nodo_nom, '_flat') == nil) then if verja_vecina(lx, ly, lz+1) then vecinos = vecinos + 1; end if verja_vecina(lx+1, ly, lz) then vecinos = vecinos + 2; end if verja_vecina(lx, ly, lz-1) then vecinos = vecinos + 4; end if verja_vecina(lx-1, ly, lz) then vecinos = vecinos + 8; end else vecinos = 20 + nodo_p2; end destino:write(string.format(' verja(%d, %d, %d, %d);\n', ox, oy, oz, vecinos)); elseif valor == 7 then destino:write(string.format(' escalera(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif valor == 8 then if (string.find(nodo_nom, 'open') == nil) then nodo_p2 = -1; end destino:write(string.format(' trampa(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif valor == 9 then destino:write(string.format(' antorcha(%d, %d, %d, %d);\n', ox, oy, oz, nodo_p2)); elseif athis.bloquesignorar[nodo_nom] == nil then destino:write(string.format(' // Nodo(%d, %d, %d) [%s], p2:%d);\n', ox, oy, oz, nodo_nom, nodo_p2)); end end end if longitud > 0 then destino:write(string.format(' bloque(%d, %d, %d, %d);\n', ox, inicio, oz, longitud)); end end end destino:write('}\n'); destino:close(); end } -- Registro de privilegio específico minetest.register_privilege( "exportar", { description = "Permite exportar mapas a fichero", give_to_singleplayer = true }); -- Registro del comando del chat minetest.register_chatcommand( "openscad", { params = "x1 y1 z1 x2 y2 z2 [fichero]", description = "Exporta mapa definido por el cubo (x1,y1,z1) a (x2,y2,z2) en formato OpenSCAD.", privs = { exportar = true }, func = function(nombre, parametros) local lp = string.split(parametros, " ") local x1 = tonumber(lp[1]); local y1 = tonumber(lp[2]); local z1 = tonumber(lp[3]); local x2 = tonumber(lp[4]); local y2 = tonumber(lp[5]); local z2 = tonumber(lp[6]); local nf = lp[7]; if z2 == nil then return false, "Error en número de parámetros."; end -- Nombre del fichero if nf == nil then nf = nombre .. ".scad"; else nf = nf .. ".scad"; end -- Comprueba e intercambia las coordenadas invertidas local cambio if x1 > x2 then cambio = x2; x2 = x1; x1 = cambio; end if y1 > y2 then cambio = y2; y2 = y1; y1 = cambio; end if z1 > z2 then cambio = z2; z2 = z1; z1 = cambio; end -- Comprueba límites antes de exportar if (x2 - x1) > 150 then return false, string.format("Error en longitud X (%d -> %d) > 150.", x1, x2); elseif (z2 - z1) > 150 then return false, string.format("Error en longitud Z (%d -> %d) > 150.", z1, z2); elseif (y2 - y1) > 150 then return false, string.format("Error en altura Y (%d -> %d) > 150.", y1, y2); else exportanodos:exportabloques(nf, x1, y1, z1, x2, y2, z2); return true, "Exportado a " .. nf; end end });
Configuración específica de minetest
Para empezar lo ideal es partir de una superficie lo más limpia y plana posible, donde se fácil empezar a construir, y donde tengamos más libertad de movimientos (como “volar”) para ir depositando los bloques que conformarán nuestra construcción a imprimir. Tampoco nos interesa que “pase el tiempo” y se nos haga de noche.
NOTA: en las nuevas veriosnes de minetest > 4.15 es posible ajustar muchos de estos parámetros desde los paneles de “Configuración” → “Configuración avanzada”
Edita el fichero “/home/alumno/.minetest/minetest.conf” y añade/ajusta las siguientes variables:
cinematic = false creative_mode = true enable_3d_clouds = false enable_damage = false enable_particles = false enable_shaders = false fixed_map_seed = 81072245 leaves_style = opaque mg_name = v6 mg_flags = flat name = server_dedicated = false smooth_lighting = false free_move = true fast_move = true no_clip = true doubletap_jump = true time_speed = 0
Creación de un mundo “plano” y selección del “mod”
Abre de nuevo el programa y verifica que el módulo que hemos creado aparece disponible en la solapa “Mods”:
En la solapa “Un jugador” activa la opción “Modo creativo” y desactiva “Permitir daños”. A continuación pulsa sobre “Nuevo” para crear un nuevo mapa.
Dale un nombre al “mundo”. Opcionalmente dale una semilla al generador (81072245 funciona bien). Utiliza la versión “Flat” (o en su defecto «V6«) y el tipo de juego “Minetest”. Prueba otras combinaciones. Pulsa el botón “Crear” para continuar:
De vuelta a la pantalla de “Un Jugador” selecciona el mundo recién creado y pulsa sobre “Configurar”:
Pulsa sobre el módulo “openscad” que hemos creado y asegúrate de marcar la casilla “Activado” para que se utilice en este mundo en particular. Después pulsa en “Guardar”.
De vuelta a la pantalla de “Un Jugador” ya puedes seleccionar el mundo y pulsar sobre el botón “Jugar”:
Si todo ha ido bien deberías ver una pantalla muy similar a esta:
Apunta con el ratón y muévete con las teclas:
W – Adelante
S – Atrás
A – Izquierda
D – Derecha
Espacio – Saltar
K – Volar (si/no)
J – Ir rápido (si/no)
H – Atravesar paredes (si/no)
Para que funcionen las tres últimas acciones hay que “dar permiso” al jugador. Pulsa la tecla F10 y escribe en la consola:
/grant singleplayer fly /grant singleplayer fast /grant singleplayer all
Para salir de la consola pulsa de nuevo F10.
Los controles de vuelo son
Espacio – para ascender
Shif(mayúsculas) – para descender
NOTA: si juegas en un servidor será el administrador el que tenga que darte los permisos sustituyendo “singleplayer” por tu nombre de jugador.
Configuración de los bloques (materiales) a utilizar
Aunque en el script del módulo se ha procurado incluir la mayoría de los materiales «imprimibles» (el agua, fuego, lava, el cristal, árboles, flores y demás elementos decorativos no se contemplan como tal) vamos a procurar utilizar sólo los materiales de tipo “bloque”. Te recomiendo empezar con los siguientes tipos:
- dirt – Para la tierra
- dirt_with_grass – Para la superficie sobre la que construir
- stone – Para la construcción
Abre el inventario con la letra “i”, busca estos tipos de bloque (páginas 2 y 5) y colócalos en la rejilla central. Pulsa la “i” de nuevo para salir.
NOTA: si dejas el cursor encima de un bloque te mostrará el nombre de éste.
Creación del modelo
Selecciona el material a utilizar con las teclas 1, 2 y 3. Pulsa con el botón derecho del ratón para depositar un bloque, y con el izquierdo para destruirlo.
Para indicar dónde aparecerá el bloque o cual va a ser destruido utiliza la cruz central de la pantalla. Esta “marcará” las caras del bloque señalado. Asegúrate de tener bien apuntada la cara donde se “pegará” en bloque que vayas a crear.
TRUCOS y NOTAS:
- Puedes empezar dibujando en el suelo una base sobre la que levantar el resto de la estructura y que delimite su extensión (máximo 150 bloques de lado).
- Si dejas el botón del ratón pulsado se repite la acción (poner/destruir) hasta que lo sueltas, lo que te permite diseñar más rápidamente.
- Cierra bien las esquinas y los «tejados» (evita bloques que se «toquen» sólo por los vértices -> deben estar «pegados» por las caras).
- Existen complementos (módulos) gratuitos que puedes añadir a tu minetest para mejorar las tareas de construcción (copiar/cortar/pegar/repetir grupos de bloqes…), como “worldEdit”:
https://forum.minetest.net/viewtopic.php?id=572 - Cuidado con los puentes, puertas, ventanas, balcones… Las impresoras 3D no pueden imprimir en el aire. Cualquier hueco de más de tres bloques o voladizo de más de 1 bloque podría imprimirse mal y arruinar el modelo.
- Céntrate en el exterior del modelo. No pierdas tiempo diseñando interiores que no se van a ver.
- Recuerda que por ahora imprimimos en 1 sólo color, por lo que las decoraciones con bloques de distintos tonos y/o colores no se mostrarán como tal en la pieza impresa!
Uso del módulo y exportación
Coordenadas
El mundo de Minetest es un gran cubo. Y debido a esto, una posición en el mundo puede serww fácilmente expresada con coordenadas cartesianas. Es decir, para cada posición en el mundo, hay 3 valores: X, Y y Z.
Las coordenadas se expresan así: (5, 45, -12). Esto se refiere a la posición donde X = 5, Y = 45 y Z = -12. Las 3 letras se llaman «ejes»: Y es para la altura. X y Z son para la posición horizontal. El tamaño de un bloque es 1 unidad.
Para exportar nuestro modelo tenemos que averiguar desde que esquina a que esquina opuesta están localizados nuestros bloques. Para “recortar” también el suelo recuerda reducir 1 o 2 los valores de la Y inferior.
Sitúate cerca de una esquina de tu construcción:
- Pulsa la tecla F7 hasta que se muestre a tu personaje. Asegúrate de estar a la altura del suelo.
- Pulsa la tecla F5 para mostrar los mensajes del depurador: Al principio de la segunda línea de datos que aparece en la parte superior de la pantalla podrás ver las coordenadas de tu personaje en el mundo.
- En mi caso marca (-0.8, 3.5, -7.2). Este será mi punto (A). Como quiero coger 1 fila de base: Y: 3.5 – 1 = 2.5. Apúntalo para más adelante.
Sitúate ahora en la esquina opuesta del rectángulo imaginario que abarque tu construcción. Apunta de nuevo tus coordenadas.
En mi caso el punto (B) sería (-14.2, 3.5, 7.2). Como he construido 6 líneas de bloques por encima del suelo añadiré por lo menos 6 al valor de la Y: 3.5 + 6 = 9.5. (puedes ser generoso/a, el «aire» por encima no se imprime).
Me quedan:
A = -0.8, 2.5, -7.2
B = -14.2, 9.5, 7.2
Con estos datos ejecuta el comando que hemos definido en nuestro mod: pulsa la tecla F10 y escribe:
/openscad -0.8 2.5 -7.2 -14.2 9.5 7.2
El fichero generado se habrá guardado en la carpeta “del mundo” actual. Opcionalmente puedes pasarle como último parámetro el nombre del fichero a exportar (sin extensión). En caso contrario tomará el nombre del usuario que invoca el script.
En mi caso y como se aprecia en la figura en la carpeta “.minetest/worlds” de mi usuario:
.minetest/worlds/ └── Castillo de David/ ├── auth.txt ├── env_meta.txt ├── force_loaded.txt ├── ipban.txt ├── map_meta.txt ├── map.sqlite ├── singleplayer.scad ├── players/ │ └── singleplayer └── world.mt
NOTAS:
-
Los decimales en las coordenadas se deben a la posición parcial del usuario sobre un bloque. Puedes redondear los valores al entero mayor que les corresponda.
-
Si sales y vuelves a entrar al mundo asegúrate de recorrer de nuevo todo el espacio antes de exportar para que minetest lo regenere internamente o el modelo puede aparecer incompleto.
Generar e imprimir el modelo 3D
Generar el modelo STL 3D final
Invoca el programa openscad pasándole como parámetro la ruta del fichero generado, o bien utiliza su menú “Archivo” → “Abrir”.
Pulsa a continuación la tecla F6 (o menú “Diseñar” → “Render”) para obtener un modelo sólido. Cuando termine el proceso utiliza el menú “Archivo” → “Expoportar” → “Exportar como STL”
Generar el g-code para la impresora
Abre ahora el fichero STL anterior con el programa “Slic3r” o “Cura”. Carga la configuración para tu modelo de impresora y filamento y genera el fichero .gcode a enviar a la impresora.
Resultado
Con el modelo del ejemplo y tras 25 minutos de impresión este fue el resultado final (y no, no imprimimos monedas, es para ver la escala):
.. y estos fueron los de los chicos….
Sugerencias para prácticas en casa o en clase:
- Modelado e impresión de las iglesias y/o monumentos más representativos de tu ciudad
- Diseño e impresión por piezas de una villa romana, ¡o de Roma!
- Lo mismo para la Acrópolis de Atenas
- Las “arenas” del juego Clash Royale
- Llaveros con forma de “chapa” y con tu nombre
- Un plano en relieve para invidentes de tu colegio, chapas con los nombres de las clases, laboratorios, biblioteca, secretaría….
- Laberintos por el que luego pueda moverse un pequeño rodamiento (bola, canica) para jugar
- Las piezas del juego del ajedrez
- Soportes para tabletas o móviles
- ….
Trabaja si puedes por equipos sobre un mismo mundo (servidor). Es mucho más divertido!
Notas sobre el módulo que exporta a openSCAD:
Se trata de una primera reversión que habrá que seguir mejorando para que incluya otro tipo de bloques como vegetación, mobiliario, objetos….Por ahora se ha mejorado la entrada de parámetros, añadidos algunos controles para evitar errores, limitada la exportación a un cubo de 150 bloques de lado, reconocido bloques especiales como antorchas, escalones, vallas, escaleras, puertas, trampillas.. en sus diferentes posiciones. Por último se ha establecido un permiso especial llamado «exportar» para poder utilizar el módulo por parte de un jugador.
Cuanto más grande y complejo sea el mapa a exportar más le costará luego a openSCAD hacer los cálculos. ¡Paciencia!