Cool. Yeah. Metaballs could be interesting. I was thinking of actually traversing voxels in each coordinate direction and merging islands. I still don't trust booleans much, but it is worth a try.spin wrote:Yes, Blender supports n-gons, and recently they gave a huge boost to Boolean operations since the modifier was rewritten to use them.
I have no idea how to make a surface-only mesh from Minetest. For my purpose I care only about geometry, not textures, since FFF printers are rather monochromatic.
The approaches to building a single geometry can be different. Either:
- we try to merge the cubes using boolean operations as they are imported.
- we detect outermost faces by, idk, calculating mesh proximity or overlaps, and then remove doubles inside Blender to have them merged.
- we detect outermost vertices of cubes, and build a smooth mesh by connecting them together (in which case the result is smooth, and we treat Minetest's cubes kind of like metaballs)
-we spawn metaballs instead of cubes and let Blender do the processing :P
- something completely different. Meshlab + point cloud, idk.
Since materials aren't interesting, here's the above mod I posted, modified to export a 3D (on/off) bitmap instead of "color" data:
Code: Select all
-- Block Export [blockexport] mod. All code is WTFPL.
-- Author: prestidigitator (as registered on forum.minetest.net)
--
-- This mod defines a new chat command "/exportblock" which writes 3D node data
-- of the player's current 80x80x80 node block to a file. This chat command
-- requires the (new) "export" privilege.
--
-- The export file format is as follows:
-- xMin,yMin,zMin
-- rowData1
-- rowData2
-- ...
--
-- where rowData is a sequence of hex digits, each representing a bitmask of
-- booleans for block presence. Each bit corresponds with a node having one of
-- the blocks identified in the EXPORT_NODES array. The row data is indexed by
-- x coordinate (with 80/4 = 20 entries), with the most significant bit of each
-- hex digit representing the first node. The first row is at y = yMin and
-- z = zMin, and they are ordered with y incrementing most often (so the second
-- row is at y = yMin+1, z = zMin). There will be exactly 80*80 = 6400 lines
-- with row data.
--
-- The file exported is in the world directory, named
-- "<prefix><bx>-<by>-<bz><suffix>", where <prefix> is FILE_PREFIX, <suffix>
-- is FILE_SUFFIX, and <bx>, <by>, and <bz> are the (minimum) x, y, and z
-- coordinates of the exported block, with negative values prefixed by a "m"
-- character rather than a minus sign. For example, with the default prefix of
-- "block_" and default suffix of ".mtb", exporting the block just
-- north/east/above the origin would write "block_0-0-0.mtb" and exporting the
-- one just south/west/below would write "block_m1-m1-m1.mtb". Each exported
-- block file is very close to 1 MB in size.
---- Configuration Data: Modify to customize behavior
local FILE_PREFIX = "block_";
local FILE_SUFFIX = ".mtb";
local EXPORT_NODES = { "wool:white", "wool:red", "wool:green", "wool:blue" };
---- End Configuration Data
local MOD_NAME = minetest.get_current_modname();
if #EXPORT_NODES > 255 then
error(MOD_NAME .. ": too many export node names");
end
local contentIdExported;
do
local EXPORT_MAP = nil;
contentIdExported = function(cid)
if not EXPORT_MAP then
EXPORT_MAP = {};
for _, name in ipairs(EXPORT_NODES) do
local cid = minetest.get_content_id(name);
EXPORT_MAP[cid] = true;
end
end
return EXPORT_MAP[cid] or false;
end;
end
local function fileName(pos)
local xs = math.floor(pos.x);
local ys = math.floor(pos.y);
local zs = math.floor(pos.z);
if xs >= 0 then xs = tostring(xs); else xs = "m" .. tostring(-xs); end
if ys >= 0 then ys = tostring(ys); else ys = "m" .. tostring(-ys); end
if zs >= 0 then zs = tostring(zs); else zs = "m" .. tostring(-zs); end
return FILE_PREFIX .. xs .. "-" .. ys .. "-" .. zs .. FILE_SUFFIX;
end
local function toHexDigit(value)
value = math.floor(value) % 16;
return string.format("%x", value);
end
local function exportBlock(pos)
local bx = 80 * math.floor(pos.x / 80);
local by = 80 * math.floor(pos.y / 80);
local bz = 80 * math.floor(pos.z / 80);
local bmin = { x = bx, y = by, z = bz };
local bmax = { x = bx + 79, y = by + 79, z = bz + 79 };
local vm = minetest.get_voxel_manip();
bmin, bmax = vm:read_from_map(bmin, bmax);
local data = vm:get_data();
local va = VoxelArea:new({ MinEdge = bmin, MaxEdge = bmax });
local fname = fileName(bmin);
local fpath = minetest.get_worldpath() .. "/" .. fname;
local file, emsg = io.open(fpath, "w");
if not file then error(emsg); end
file:write(bx .. "," .. by .. "," .. bz .. "\n");
for zi = 0, 79 do
for yi = 0, 79 do
for xdi = 0, 79, 4 do
local bitMap = 0;
for xi = 0, 3 do
local pos = { x = bx + xdi + xi, y = by + yi, z = bz + zi };
bitMap = 2*bitMap;
if va:containsp(pos) then
local cid = data[va:indexp(pos)];
if contentIdExported(cid) then
bitMap = bitMap + 1;
end
end
end
file:write(toHexDigit(bitMap));
end
file:write("\n");
end
end
file:flush();
file:close();
return fname;
end
minetest.register_privilege(
"export",
{
description = "Allows exporting of data to files",
give_to_singleplayer = true
});
minetest.register_chatcommand(
"exportblock",
{
params = "",
description = "Exports your current 80x80x80 node block to a file",
privs = { export = true },
func =
function(name, paramStr)
local player = minetest.get_player_by_name(name);
local fname = exportBlock(player:getpos());
return true, "current block exported to " .. fname;
end
});