├── README.md ├── add-voxel-mesh-menu.png ├── blender-image-voxel-open-graph.afphoto ├── blender-image-voxel-open-graph.png ├── example-02.png ├── example.png ├── image_voxel_add_on.py ├── license.txt └── voxel-options.png /README.md: -------------------------------------------------------------------------------- 1 | # Blender Image Voxel add-on 2 | 3 | ![example mesh created with image voxel add-on](./example.png) 4 | 5 | ## Install instructions: 6 | 7 | 1. Download `image_voxel_add_on.py` 8 | 2. In the Blender preferences menu, select the Add-ons tab in the left. 9 | 3. Click on the `Instal ...` button in the top right of the interface. 10 | 4. Browse and select the downloaded `image_voxel_add_on.py` script, 11 | and then click on the `Install Add-on` button. 12 | 5. Back in the Add-ons tab, make sure the checkbox next to the "Add Mesh: Image To Voxel" is ticked. 13 | 14 | ## License: 15 | 16 | - [MIT License](license.txt) 17 | 18 | ## What is it? 19 | 20 | This add-on adds a new menu item to the 3D view `Add Mesh` menu 21 | with the label `Add Voxel Mesh`. When you select that item a small dialog 22 | opens asking for the final size of the mesh (width) and the source Image Data 23 | Block. **The Image data block must already exist**. 24 | 25 | ![add mesh menu with add image voxel add-on](./add-voxel-mesh-menu.png) 26 | ![options for create voxel mesh](./voxel-options.png) 27 | 28 | I have used this plugin with images as large as 2000x2000 pixels, but I had to 29 | wait a while for the mesh to generate. YMMV, I suggest starting experiments with 30 | smaller images. 31 | 32 | Meshes and objects created with this add-on are named `Voxel`, and appear in a 33 | collection named `voxels`. 34 | 35 | Meshes created with this add-on have a vertex color group called `voxel colors` 36 | 37 | ## History: 38 | 39 | This addon is the evolution to some [simple gists](https://gist.github.com/knowuh/48136d7a17387e7cf6c3). 40 | This version: 41 | 42 | - Adds a little bit of UI. 43 | - Generates one mesh object instead of many. This makes it easier to manipulate the results. 44 | - Uses the Blender BMesh python module to generate the mesh faster. 45 | - Allows you to specify the final image size. 46 | - Uses a vertex color layer to store pixel color data. 47 | 48 | -------------------------------------------------------------------------------- /add-voxel-mesh-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/add-voxel-mesh-menu.png -------------------------------------------------------------------------------- /blender-image-voxel-open-graph.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/blender-image-voxel-open-graph.afphoto -------------------------------------------------------------------------------- /blender-image-voxel-open-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/blender-image-voxel-open-graph.png -------------------------------------------------------------------------------- /example-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/example-02.png -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/example.png -------------------------------------------------------------------------------- /image_voxel_add_on.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator 3 | from bpy.props import StringProperty, FloatProperty 4 | from bpy_extras.object_utils import AddObjectHelper 5 | from mathutils import Vector 6 | import bmesh 7 | import colorsys 8 | 9 | bl_info = { 10 | "name": "Image To Voxel", 11 | "author": "Noah Paessel", 12 | "version": (1, 0), 13 | "blender": (2, 80, 0), 14 | "location": "View3D > Add > Mesh > Image Voxel", 15 | "description": "Adds a new Mesh Object generated from an image", 16 | "warning": "", 17 | "doc_url": "", 18 | "category": "Add Mesh", 19 | } 20 | 21 | material_name = "Image Voxel Material" 22 | vertex_color_layer_name = "Vertex Color" 23 | collection_name = "voxels" 24 | class VoxelFactory: 25 | def __init__(self, imageName, display_size=2, baseScale=1, topScale=0.5): 26 | self.image = bpy.data.images[imageName] 27 | self.size = self.image.size 28 | self.scale = display_size / self.size[0] 29 | self.topScale = topScale 30 | self.baseScale = baseScale 31 | self.offset = self.scale / 2 32 | self.baseOffset = (self.scale * baseScale) / 2 33 | self.topOffset = (self.scale * topScale) / 2 34 | self.collection = bpy.data.collections.get(collection_name) 35 | if self.collection is None: 36 | self.collection = bpy.data.collections.new(collection_name) 37 | bpy.context.scene.collection.children.link(self.collection) 38 | 39 | self.bmesh = bmesh.new() 40 | self.color_layer = self.bmesh.loops.layers.color.new(vertex_color_layer_name) 41 | 42 | 43 | def get_color(self, x, y): 44 | stride = self.image.channels 45 | width = self.size[0] 46 | index = x * stride + y * stride * width 47 | return self.image.pixels[index: index + self.image.channels] 48 | 49 | def make_material(self): 50 | mat = bpy.data.materials.get(material_name) 51 | if mat is None: 52 | mat = bpy.data.materials.new(material_name) 53 | mat.use_nodes = True 54 | nodes = mat.node_tree.nodes 55 | principled_bsdf_node = nodes.get("Principled BSDF") 56 | vertex_color_node = None 57 | if not "VERTEX_COLOR" in [node.type for node in nodes]: 58 | vertex_color_node = nodes.new(type = "ShaderNodeVertexColor") 59 | else: 60 | vertex_color_node = nodes.get("Vertex Color") 61 | vertex_color_node.layer_name = vertex_color_layer_name 62 | links = mat.node_tree.links 63 | link = links.new(vertex_color_node.outputs[0], principled_bsdf_node.inputs[0]) 64 | return mat 65 | 66 | def make_voxel_geom(self, x, y): 67 | color = self.get_color(x, y) 68 | red,green,blue,alpha = color 69 | h,l,s = colorsys.rgb_to_hls(red,green,blue) 70 | height = l * (10 * self.scale) 71 | 72 | # The Offset points of a rectangular prism: 73 | x = x * self.scale 74 | y = y * self.scale 75 | # Base: 76 | a = self.bmesh.verts.new((x - self.baseOffset, y - self.baseOffset, 0)) 77 | b = self.bmesh.verts.new((x + self.baseOffset, y - self.baseOffset, 0)) 78 | c = self.bmesh.verts.new((x + self.baseOffset, y + self.baseOffset, 0)) 79 | d = self.bmesh.verts.new((x - self.baseOffset, y + self.baseOffset, 0)) 80 | 81 | # Top: 82 | e = self.bmesh.verts.new((x - self.topOffset, y - self.topOffset, height)) 83 | f = self.bmesh.verts.new((x + self.topOffset, y - self.topOffset, height)) 84 | g = self.bmesh.verts.new((x + self.topOffset, y + self.topOffset, height)) 85 | h = self.bmesh.verts.new((x - self.topOffset, y + self.topOffset, height)) 86 | 87 | # The Faces of the rectangular prism: 88 | fa = self.bmesh.faces.new([a,b,c,d]) 89 | fb = self.bmesh.faces.new([b,f,e,a]) 90 | fc = self.bmesh.faces.new([f,g,h,e]) 91 | fd = self.bmesh.faces.new([g,c,d,h]) 92 | fe = self.bmesh.faces.new([a,e,h,d]) 93 | ff = self.bmesh.faces.new([c,g,f,b]) 94 | for face in (fa, fb, fc, fd, fe, ff): 95 | for loop in face.loops: 96 | loop[self.color_layer] = (red,green,blue,1) 97 | 98 | # Create a voxel at the given location. 99 | # Add it to the scene and to the voxel collection. 100 | def make_object(self, xx, yy): 101 | width, height = self.size 102 | for x in range(width): 103 | for y in range(height): 104 | self.make_voxel_geom(x, y) 105 | 106 | mesh = bpy.data.meshes.new("Voxel") 107 | self.bmesh.to_mesh(mesh) 108 | mesh.update() # required? 109 | mesh.materials.append(self.make_material()) 110 | 111 | obj = bpy.data.objects.new("Voxel", mesh) 112 | obj.location = (xx, yy, 0) 113 | 114 | self.collection.objects.link(obj) 115 | return obj 116 | 117 | class AddImageVoxel(Operator): 118 | """Create a new Voxel Image Object""" 119 | bl_idname = "mesh.add_voxel" 120 | bl_label = "Add Voxel Object" 121 | bl_options = {'REGISTER', 'UNDO'} 122 | 123 | display_size: bpy.props.FloatProperty(name="Display Size", default=2, min=0.1, max=10) 124 | voxel_image: bpy.props.StringProperty(name="Source Image DATA", default="") 125 | 126 | def execute(self, context): 127 | voxel_image = self.voxel_image 128 | vf = VoxelFactory(voxel_image, display_size=2) 129 | vf.make_object(0, 0) 130 | return {'FINISHED'} 131 | 132 | def invoke(self, context, event): 133 | wm = context.window_manager 134 | return wm.invoke_props_dialog(self) 135 | 136 | def draw(self, context): 137 | layout = self.layout 138 | scene = context.scene 139 | col = layout.column() 140 | col.label(text="Generate Voxel Mesh:") 141 | col = layout.column() 142 | col.prop_search(self, "voxel_image", bpy.data, "images") 143 | col = layout.column() 144 | col.prop(self, "display_size") 145 | 146 | # Only needed if you want to add into a dynamic menu 147 | def menu_func(self, context): 148 | self.layout.operator(AddImageVoxel.bl_idname, text="Add Voxel Mesh") 149 | 150 | def register(): 151 | bpy.utils.register_class(AddImageVoxel) 152 | bpy.types.VIEW3D_MT_mesh_add.append(menu_func) 153 | 154 | def unregister(): 155 | bpy.utils.unregister_class(AddImageVoxel) 156 | bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) 157 | 158 | if __name__ == "__main__": 159 | register() -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 Noah Paessel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /voxel-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knowuh/blender-image-voxel/a8d3070b1f95d8c13c845ac30705a69c297e906c/voxel-options.png --------------------------------------------------------------------------------