self.priority = 8 self.api.register_tool(self) # Color the targeted voxel def on_mouse_click(self, data): data.voxels.clear_selection() # If we have a voxel at the target, get its color. voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel: self.api.set_palette_color(voxel) if data.mouse_button == MouseButtons.LEFT: # Darken the color. newColor = self.api.get_palette_color().darker(110) # set the voxel's new color. data.voxels.set(data.world_x, data.world_y, data.world_z, newColor) elif data.mouse_button == MouseButtons.RIGHT: # Lighten the color. newColor = self.api.get_palette_color().lighter(110) # set the voxel's new color. data.voxels.set(data.world_x, data.world_y, data.world_z, newColor) # Color when dragging also def on_drag(self, data): data.voxels.clear_selection() self.on_mouse_click(data) register_plugin(ShaderTool, "Shadeing Tool", "1.0")
# You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from PySide import QtGui from tool import Tool from plugin_api import register_plugin class ColorPickTool(Tool): def __init__(self, api): super(ColorPickTool, self).__init__(api) # Create our action / icon self.action = QtGui.QAction( QtGui.QPixmap(":/images/gfx/icons/pipette.png"), "Color Pick", None) self.action.setStatusTip("Choose a color from an existing voxel.") self.action.setCheckable(True) self.action.setShortcut(QtGui.QKeySequence("Ctrl+5")) # Register the tool self.priority = 5 self.api.register_tool(self) # Grab the color of the selected voxel def on_mouse_click(self, data): # If we have a voxel at the target, color it voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel: self.api.set_palette_color(voxel) register_plugin(ColorPickTool, "Color Picking Tool", "1.0")
max_width = width if height > max_height: max_height = height if depth > max_depth: max_depth = depth voxels.resize(max_width, max_height, max_depth) # Matrix position - FIXME not yet supported dx = self.uint32(f) dy = self.uint32(f) dz = self.uint32(f) # Data for z in xrange(depth): for y in xrange(height): for x in xrange(width): vox = self.uint32(f) vox = (vox & 0x00ffffff) if vox: r = (vox & 0x000000ff)>>0 g = (vox & 0x0000ff00)>>8 b = (vox & 0x00ff0000)>>16 vox = (r<<24) | (g<<16) | (b<<8) | 0xff voxels.set(x, y, z, vox) f.close() register_plugin(QubicleFile, "Qubicle Constructor file format IO", "1.0")
data.world_z) else: self._first_target = (data.world_x, data.world_y, data.world_z) if data.voxels.is_selected(data.world_x, data.world_y, data.world_z): data.voxels.deselect(data.world_x, data.world_y, data.world_z) else: data.voxels.select(data.world_x, data.world_y, data.world_z) # Start a drag def on_drag_start(self, data): voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel != 0: self._first_target = (data.world_x, data.world_y, data.world_z) # When dragging, create the selection def on_drag(self, data): # In case the first click has missed a valid target. if self._first_target is None: return voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel != 0: data.voxels.clear_selection() self.select(data, data.world_x, data.world_y, data.world_z) register_plugin(SelectionTool, "Selection Tool", "1.0")
return Face.COLLIDABLE_FACES_PLANE_X + Face.COLLIDABLE_FACES_PLANE_Z elif face in Face.COLLIDABLE_FACES_PLANE_Z: return Face.COLLIDABLE_FACES_PLANE_X + Face.COLLIDABLE_FACES_PLANE_Y else: return None # Draw a new voxel next to the targeted face def on_mouse_click(self, data): data.voxels.clear_selection() shift_down = not not data.key_modifiers & QtCore.Qt.KeyboardModifier.ShiftModifier self._first_target = self._draw_voxel( data, shift_down, data.mouse_button == MouseButtons.RIGHT) # Start a drag def on_drag_start(self, data): data.voxels.clear_selection() self._first_target = data # When dragging, Draw a new voxel next to the targeted face def on_drag(self, data): # In case the first click has missed a valid target. if self._first_target is None: return valid_faces = self._get_valid_sequence_faces(self._first_target.face) if (not valid_faces) or (data.face not in valid_faces): return self._draw_voxel(data, False, False) register_plugin(DrawingTool, "Drawing Tool", "1.0")
from plugin_api import register_plugin class PaintingTool(Tool): def __init__(self, api): super(PaintingTool, self).__init__(api) # Create our action / icon self.action = QtGui.QAction( QtGui.QPixmap(":/images/gfx/icons/paint-brush.png"), "Paint", None) self.action.setStatusTip("Colour Voxels") self.action.setCheckable(True) # Register the tool self.api.register_tool(self) # Colour the targeted voxel def on_activate(self, target, mouse_position): # If we have a voxel at the target, colour it voxel = target.voxels.get(target.x, target.y, target.z) if voxel: target.voxels.set(target.x, target.y, target.z, self.colour) # Colour when dragging also def on_drag(self, target, mouse_position): # If we have a voxel at the target, colour it voxel = target.voxels.get(target.x, target.y, target.z) if voxel: target.voxels.set(target.x, target.y, target.z, self.colour) register_plugin(PaintingTool, "Painting Tool", "1.0")
return Face.COLLIDABLE_FACES_PLANE_Y + Face.COLLIDABLE_FACES_PLANE_Z elif face in Face.COLLIDABLE_FACES_PLANE_Y: return Face.COLLIDABLE_FACES_PLANE_X + Face.COLLIDABLE_FACES_PLANE_Z elif face in Face.COLLIDABLE_FACES_PLANE_Z: return Face.COLLIDABLE_FACES_PLANE_X + Face.COLLIDABLE_FACES_PLANE_Y else: return None # Draw a new voxel next to the targeted face def on_mouse_click(self, data): data.voxels.clear_selection() shift_down = not not data.key_modifiers & QtCore.Qt.KeyboardModifier.ShiftModifier self._first_target = self._draw_voxel(data, shift_down, data.mouse_button == MouseButtons.RIGHT) # Start a drag def on_drag_start(self, data): data.voxels.clear_selection() self._first_target = data # When dragging, Draw a new voxel next to the targeted face def on_drag(self, data): # In case the first click has missed a valid target. if self._first_target is None: return valid_faces = self._get_valid_sequence_faces(self._first_target.face) if (not valid_faces) or (data.face not in valid_faces): return self._draw_voxel(data, False, False) register_plugin(DrawingTool, "Drawing Tool", "1.0")
else: # Zoxel file with no dimension data, determine size maxX = -127 maxY = -127 maxZ = -127 for x, y, z, v in frame: if x > maxX: maxX = x if y > maxY: maxY = y if z > maxZ: maxZ = z # Resize voxels.resize(maxX + 1, maxY + 1, maxZ + 1) # Read the voxel data for f in xrange(frames): frame = data['frame{0}'.format(f + 1)] for x, y, z, v in frame: voxels.set(x, y, z, v) # Add another frame if required if f < frames - 1: voxels.add_frame(False) # Select the first frame by default if frames > 1: voxels.select_frame(0) register_plugin(ZoxelFile, "Zoxel file format IO", "1.0")
self.action.setCheckable(True) self.action.setShortcut(QtGui.QKeySequence("Ctrl+8")) # Register the tool self.priority = 8 self.api.register_tool(self) # Color the targeted voxel def on_mouse_click(self, data): data.voxels.clear_selection() # If we have a voxel at the target, get its color. voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel: self.api.set_palette_color(voxel) if data.mouse_button == MouseButtons.LEFT: # Darken the color. newColor = self.api.get_palette_color().darker(110) # set the voxel's new color. data.voxels.set(data.world_x, data.world_y, data.world_z, newColor) elif data.mouse_button == MouseButtons.RIGHT: # Lighten the color. newColor = self.api.get_palette_color().lighter(110) # set the voxel's new color. data.voxels.set(data.world_x, data.world_y, data.world_z, newColor) # Color when dragging also def on_drag(self, data): data.voxels.clear_selection() self.on_mouse_click(data) register_plugin(ShaderTool, "Shadeing Tool", "1.0")
# 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, see <http://www.gnu.org/licenses/>. from PySide import QtGui from tool import Tool from plugin_api import register_plugin class ColorPickTool(Tool): def __init__(self, api): super(ColorPickTool, self).__init__(api) # Create our action / icon self.action = QtGui.QAction(QtGui.QPixmap(":/images/gfx/icons/pipette.png"), "Color Pick", None) self.action.setStatusTip("Choose a color from an existing voxel.") self.action.setCheckable(True) self.action.setShortcut(QtGui.QKeySequence("Ctrl+5")) # Register the tool self.priority = 5 self.api.register_tool(self) # Grab the color of the selected voxel def on_mouse_click(self, data): # If we have a voxel at the target, color it voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel: self.api.set_palette_color(voxel) register_plugin(ColorPickTool, "Color Picking Tool", "1.0")
# # 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, see <http://www.gnu.org/licenses/>. from PySide import QtGui from tool import Tool from plugin_api import register_plugin class EraseTool(Tool): def __init__(self, api): super(EraseTool, self).__init__(api) # Create our action / icon self.action = QtGui.QAction( QtGui.QPixmap(":/images/gfx/icons/shovel.png"), "Erase", None) self.action.setStatusTip("Erase voxels") self.action.setCheckable(True) # Register the tool self.api.register_tool(self) # Clear the targeted voxel def on_activate(self, target, mouse_position): target.voxels.set(target.x, target.y, target.z, 0) register_plugin(EraseTool, "Erasing Tool", "1.0")
if ctrl_down: self.select(data, data.world_x, data.world_y, data.world_z, True) self._first_target = (data.world_x, data.world_y, data.world_z) elif shift_down: self.select(data, data.world_x, data.world_y, data.world_z) self._first_target = (data.world_x, data.world_y, data.world_z) else: self._first_target = (data.world_x, data.world_y, data.world_z) if data.voxels.is_selected(data.world_x, data.world_y, data.world_z): data.voxels.deselect(data.world_x, data.world_y, data.world_z) else: data.voxels.select(data.world_x, data.world_y, data.world_z) # Start a drag def on_drag_start(self, data): voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel != 0: self._first_target = (data.world_x, data.world_y, data.world_z) # When dragging, create the selection def on_drag(self, data): # In case the first click has missed a valid target. if self._first_target is None: return voxel = data.voxels.get(data.world_x, data.world_y, data.world_z) if voxel != 0: data.voxels.clear_selection() self.select(data, data.world_x, data.world_y, data.world_z) register_plugin(SelectionTool, "Selection Tool", "1.0")
x = self.uint32(f) y = self.uint32(f) z = self.uint32(f) voxels.resize(x, z, y) # read palette chunk palette = [] if paletteBegin == -1 or paletteLength == -1: palette = self.default_palette else: f.seek(paletteBegin) for i in xrange(0, paletteLength / 4): palette.append(self.uint32(f)) # read voxel chunk f.seek(voxelBegin) voxelCount = self.uint32(f) for i in xrange(0, voxelCount): vox = self.uint32(f) ix = (vox & 0x000000ff) >> 0 iy = (vox & 0x0000ff00) >> 8 iz = (vox & 0x00ff0000) >> 16 ip = (vox & 0xff000000) >> 24 b = (palette[ip - 1] & 0x00ff0000) >> 16 g = (palette[ip - 1] & 0x0000ff00) >> 8 r = (palette[ip - 1] & 0x000000ff) >> 0 voxels.set(ix, iz, iy, (r << 24) | (g << 16) | (b << 8) | 0xff) f.close() register_plugin(MagicaFile, "Magica Voxel (.vox) file format IO", "1.0")
from PySide2 import QtGui, QtWidgets from tool import Tool from plugin_api import register_plugin class EraseTool(Tool): def __init__(self, api): super(EraseTool, self).__init__(api) # Create our action / icon self.action = QtWidgets.QAction( QtGui.QPixmap(":/images/gfx/icons/shovel.png"), "Erase", None) self.action.setStatusTip("Erase voxels") self.action.setCheckable(True) self.action.setShortcut(QtGui.QKeySequence("Ctrl+3")) # Register the tool self.priority = 3 self.api.register_tool(self) # Clear the targeted voxel def on_mouse_click(self, data): if len(data.voxels._selection) > 0: for x, y, z in data.voxels._selection: data.voxels.set(x, y, z, 0, True, 1) data.voxels.completeUndoFill() data.voxels.clear_selection() else: data.voxels.set(data.world_x, data.world_y, data.world_z, 0) register_plugin(EraseTool, "Erasing Tool", "1.0")
faces = (len(vertices) // (3 * 3)) // 2 for i in xrange(faces): n = 1 + (i * 6) r = colours[(i * 18)] g = colours[(i * 18) + 1] b = colours[(i * 18) + 2] colour = r << 24 | g << 16 | b << 8 f.write("usemtl %s\r\n" % mats[colour]) f.write("f %i %i %i\r\n" % (n, n + 2, n + 1)) f.write("f %i %i %i\r\n" % (n + 5, n + 4, n + 3)) # Tidy up f.close() # Create our material file f = open(mat_filename, "wt") for colour, material in mats.items(): f.write("newmtl %s\r\n" % material) r = (colour & 0xff000000) >> 24 g = (colour & 0xff0000) >> 16 b = (colour & 0xff00) >> 8 r = r / 255.0 g = g / 255.0 b = b / 255.0 f.write("Ka %f %f %f\r\n" % (r, g, b)) f.write("Kd %f %f %f\r\n" % (r, g, b)) f.close() register_plugin(ObjFile, "OBJ exporter", "1.0")
y = self.uint32(f) z = self.uint32(f) voxels.resize(x, z, y) # read palette chunk palette = [] if paletteBegin == -1 or paletteLength == -1: palette = self.default_palette else: f.seek(paletteBegin) for i in xrange(0, paletteLength / 4): palette.append(self.uint32(f)) # read voxel chunk f.seek(voxelBegin) voxelCount = self.uint32(f) for i in xrange(0, voxelCount): vox = self.uint32(f) ix = (vox & 0x000000ff) >> 0 iy = (vox & 0x0000ff00) >> 8 iz = (vox & 0x00ff0000) >> 16 ip = (vox & 0xff000000) >> 24 b = (palette[ip - 1] & 0x00ff0000) >> 16 g = (palette[ip - 1] & 0x0000ff00) >> 8 r = (palette[ip - 1] & 0x000000ff) >> 0 voxels.set(ix, iz, iy, (r << 24) | (g << 16) | (b << 8) | 0xff) f.close() register_plugin(MagicaFile, "Magica Voxel (.vox) file format IO", "1.0")
fill_color = c[0] << 24 | c[1] << 16 | c[2] << 8 | 0xff if search_color == fill_color: return # Initialise our search list search = set() search.add((data.world_x, data.world_y, data.world_z)) # Keep iterating over the search list until no more to do while len(search): x, y, z = search.pop() voxel = data.voxels.get(x, y, z) if not voxel or voxel != search_color: continue # Add all likely neighbours into our search list if data.voxels.get(x - 1, y, z) == search_color: search.add((x - 1, y, z)) if data.voxels.get(x + 1, y, z) == search_color: search.add((x + 1, y, z)) if data.voxels.get(x, y + 1, z) == search_color: search.add((x, y + 1, z)) if data.voxels.get(x, y - 1, z) == search_color: search.add((x, y - 1, z)) if data.voxels.get(x, y, z + 1) == search_color: search.add((x, y, z + 1)) if data.voxels.get(x, y, z - 1) == search_color: search.add((x, y, z - 1)) # Set the color of the current voxel data.voxels.set(x, y, z, self.color, True, 1) data.voxels.completeUndoFill() register_plugin(FillTool, "Fill Tool", "1.0")
and abs(tdx) > abs(tdz))): self._mouse = (data.mouse_x, data.mouse_y) self.ydir = False self.zdir = False self.pastoffset += tx self.drawstamp(data, self.pastoffset, 0, 0) elif ty != 0 and self.ydir and (not self.zdir or abs(tdy) > abs(tdz)): self._mouse = (data.mouse_x, data.mouse_y) self.xdir = False self.zdir = False self.pastoffset += ty self.drawstamp(data, 0, self.pastoffset, 0) elif tz != 0 and self.zdir: self._mouse = (data.mouse_x, data.mouse_y) self.xdir = False self.ydir = False self.pastoffset += tz self.drawstamp(data, 0, 0, self.pastoffset) def on_drag_end(self, data): data.voxels.clear_selection() dx = self.pastoffset if self.xdir else 0 dy = self.pastoffset if self.ydir else 0 dz = self.pastoffset if self.zdir else 0 for x, y, z, col in self._stamp: data.voxels.select(x + dx, y + dy, z + dz) register_plugin(ExtrudeTool, "Extrude Tool", "1.0")
# Open our file f = open(filename, "rt") size = f.readline().strip() x, y, z = size.split(",") x = int(x) y = int(y) z = int(z) voxels.resize(x, y, z) # Parse the file for fy in xrange(y - 1, -1, -1): for fz in xrange(z - 1, -1, -1): line = f.readline().strip().split(",") for fx in xrange(0, x): if line[fx] == "#00000000": continue color = line[fx][1:] r = color[:2] g = color[2:4] b = color[4:6] r = int(r, 16) g = int(g, 16) b = int(b, 16) a = 0xff v = r << 24 | g << 16 | b << 8 | a voxels.set(fx, fy, fz, v) f.readline() # discard empty line f.close() register_plugin(SproxelFile, "Sproxel file format IO", "1.0")
def load(self, filename): # load the png try: png = QtGui.QPixmap(filename) except Exception as Ex: raise Exception("This is not a valid png file: %s" % Ex) width = png.width() height = png.height() if width > 127 or height > 127: raise Exception( "The image file is too large. Maximum width and height are 127 pixels." ) depth = 1 img = png.toImage() # grab the voxel data voxels = self.api.get_voxel_data() voxels.resize(width, height, depth) for x in range(width): for y in range(height): color = img.pixel(x, y) color = color << 8 voxels.set(x, height - y - 1, 0, color) register_plugin(PngFile, "Png file format Importer", "1.0")
index += 1 if (data & 0xff000000) >> 24: voxels.set((width - x - 1), y, depth - z - 1 if coords == 1 else z, self.formatVox(data, format)) else: for z in xrange(depth): for y in xrange(height): for x in xrange(width): vox = self.uint32(f) if (vox & 0xff000000) >> 24: voxels.set((width - x - 1), y, depth - z - 1 if coords == 1 else z, self.formatVox(vox, format)) # restore attachment point if matrix_count == 1 and dx <= 0 and dy <= 0 and dz <= 0 and ( dx < 0 or dy < 0 or dz < 0): r = QMessageBox.question(None, "Restore attachment point?", ( "It looks like your are opening a voxel model exported by Trove.\nShould we" " try to restore the attachment point out of the .qb's metadata for you?" ), QMessageBox.No, QMessageBox.Yes) if r == QMessageBox.Yes: voxels.set(max_width + dx - 1, -dy, max_depth + dz - 1 if coords == 1 else -dz, 0xff00ffff) f.close() register_plugin(QubicleFile, "Qubicle Constructor file format IO", "1.0")
x, y, z = search.pop() voxel = data.voxels.get(x, y, z) if not voxel or voxel != search_color: continue # Add all likely neighbours into our search list if data.voxels.get(x - 1, y, z) == search_color and not (x - 1, y, z) in searched: search.add((x - 1, y, z)) if data.voxels.get(x + 1, y, z) == search_color and not (x + 1, y, z) in searched: search.add((x + 1, y, z)) if data.voxels.get(x, y + 1, z) == search_color and not (x, y + 1, z) in searched: search.add((x, y + 1, z)) if data.voxels.get(x, y - 1, z) == search_color and not (x, y - 1, z) in searched: search.add((x, y - 1, z)) if data.voxels.get(x, y, z + 1) == search_color and not (x, y, z + 1) in searched: search.add((x, y, z + 1)) if data.voxels.get(x, y, z - 1) == search_color and not (x, y, z - 1) in searched: search.add((x, y, z - 1)) # Set the color of the current voxel if data.mouse_button == MouseButtons.LEFT: nc = color.lighter(random() * 200 * i + 100 - 100 * i) elif data.mouse_button == MouseButtons.RIGHT: nc = QtGui.QColor(color) nc.setHsvF((nc.hueF() + (random() * 0.2 * i - 0.1 * i)) % 1, max(0, min(1, nc.saturationF() + (random() * 2 * i - i))), max(0, min(1, nc.valueF() + (random() * 2 * i - i)))) data.voxels.set(x, y, z, nc, True, 1) searched.append((x, y, z)) data.voxels.completeUndoFill() register_plugin(FillNoiseTool, "Noisy Fill Tool", "1.0")
faces = (len(vertices)//(3*3))//2 for i in xrange(faces): n = 1+(i * 6) r = colours[(i*18)] g = colours[(i*18)+1] b = colours[(i*18)+2] colour = r<<24 | g<<16 | b<<8 f.write("usemtl %s\r\n" % mats[colour]) f.write("f %i %i %i\r\n" % (n, n+2, n+1)) f.write("f %i %i %i\r\n" % (n+5, n+4, n+3)) # Tidy up f.close() # Create our material file f = open(mat_filename,"wt") for colour, material in mats.items(): f.write("newmtl %s\r\n" % material) r = (colour & 0xff000000) >> 24 g = (colour & 0xff0000) >> 16 b = (colour & 0xff00) >> 8 r = r / 255.0 g = g / 255.0 b = b / 255.0 f.write("Ka %f %f %f\r\n" % (r, g, b)) f.write("Kd %f %f %f\r\n" % (r, g, b)) f.close() register_plugin(ObjFile, "OBJ exporter", "1.0")
search.add((x, y + 1, z)) if data.voxels.get( x, y - 1, z) == search_color and not (x, y - 1, z) in searched: search.add((x, y - 1, z)) if data.voxels.get( x, y, z + 1) == search_color and not (x, y, z + 1) in searched: search.add((x, y, z + 1)) if data.voxels.get( x, y, z - 1) == search_color and not (x, y, z - 1) in searched: search.add((x, y, z - 1)) # Set the color of the current voxel if data.mouse_button == MouseButtons.LEFT: nc = color.lighter(random() * 200 * i + 100 - 100 * i) elif data.mouse_button == MouseButtons.RIGHT: nc = QtGui.QColor(color) nc.setHsvF( (nc.hueF() + (random() * 0.2 * i - 0.1 * i)) % 1, max(0, min(1, nc.saturationF() + (random() * 2 * i - i))), max(0, min(1, nc.valueF() + (random() * 2 * i - i)))) data.voxels.set(x, y, z, nc, True, 1) searched.append((x, y, z)) data.voxels.completeUndoFill() register_plugin(FillNoiseTool, "Noisy Fill Tool", "1.0")
voxels.resize(data['width'], data['height'], data['depth']) else: # Zoxel file with no dimension data, determine size maxX = -127 maxY = -127 maxZ = -127 for x, y, z, v in frame: if x > maxX: maxX = x if y > maxY: maxY = y if z > maxZ: maxZ = z # Resize voxels.resize(maxX+1, maxY+1, maxZ+1) # Read the voxel data for f in xrange(frames): frame = data['frame{0}'.format(f+1)] for x, y, z, v in frame: voxels.set(x, y, z, v) # Add another frame if required if f < frames-1: voxels.add_frame(False) # Select the first frame by default if frames > 1: voxels.select_frame(0) register_plugin(ZoxelFile, "Zoxel file format IO", "1.0")
self.drawstamp(data, 0, 0, self.pastoffset) else: if tx != 0 and self.xdir and (not self.ydir or (abs(tdx) > abs(tdy) and abs(tdx) > abs(tdz))): self._mouse = (data.mouse_x, data.mouse_y) self.ydir = False self.zdir = False self.pastoffset += tx self.drawstamp(data, self.pastoffset, 0, 0) elif ty != 0 and self.ydir and (not self.zdir or abs(tdy) > abs(tdz)): self._mouse = (data.mouse_x, data.mouse_y) self.xdir = False self.zdir = False self.pastoffset += ty self.drawstamp(data, 0, self.pastoffset, 0) elif tz != 0 and self.zdir: self._mouse = (data.mouse_x, data.mouse_y) self.xdir = False self.ydir = False self.pastoffset += tz self.drawstamp(data, 0, 0, self.pastoffset) def on_drag_end(self, data): data.voxels.clear_selection() dx = self.pastoffset if self.xdir else 0 dy = self.pastoffset if self.ydir else 0 dz = self.pastoffset if self.zdir else 0 for x, y, z, col in self._stamp: data.voxels.select(x + dx, y + dy, z + dz) register_plugin(ExtrudeTool, "Extrude Tool", "1.0")