2
$\begingroup$

I have created a script to generate a grid of cubes based on an image. So if I input a 32x32 image, I get 1024 cubes as expected. It also assigns a material to each cube based on the pixel.

But, having a new material for each cube is not good, especially when my image is an image of Mega Man and only has 5 different colors in it.

So, how do I find out whether an identical material exists so I can avoid duplicate materials. I have been searching for a long time and I can't find a way to do it. By the way, I want to do this in Cycles.

bl_info = {
    "name": "Pixel Maker",
    "author": "Nathan Craddock",
    "version": (1, 0),
    "blender": (2, 71, 0),
    "location": "In object mode on the tools tab of the tool shelf.",
    "description": "Takes an image and converts each pixel to a cube.  If the pixel is transparent, no cube is created.",
    "warning": "Work in progress",
    "category": "Object"
}

import bpy

class pixelMakerPanel(bpy.types.Panel):
    """Pixel Maker Panel"""
    bl_label = "Pixel Maker"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Tools"
    tex_type = 'IMAGE'

    def draw(self, context):
        layout = self.layout

        row = layout.row()
        row.prop(context.scene, "pixel_file_import")

        row = layout.row()
        row.prop(context.scene, "pixel_join_cubes")

        row = layout.row()
        row.scale_y = 2.0
        row.operator("object.make_pixel")

class pixelMaker(bpy.types.Operator):
    """Run the Pixel Maker Addon"""
    bl_idname = "object.make_pixel"
    bl_label = "Run Pixel Maker"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        importedImage = context.scene.pixel_file_import
        joinCubes = context.scene.pixel_join_cubes

        def setup(img):
            bpy.ops.wm.console_toggle()
            print("Creating", img)
            iteration = 0

            image = bpy.data.images[img]
            width = image.size[0]
            height = image.size[1]
            numberPix = len(image.pixels)

            for y in range(0, height):
                for x in range(0, width):
                    currentCube = (y * width) + x
                    color = []
                    for colorRGBA in range(0, 4):
                        RGBA = (currentCube * 4) + colorRGBA
                        color.append(image.pixels[RGBA])
                    drawCube(x * 2, y * 2, color)
                    iteration += 1

                print(round((iteration / (width * height)) * 100), "% completion")

            if joinCubes:
                bpy.ops.object.select_all(action='TOGGLE')    
                bpy.ops.object.select_all(action='TOGGLE')
                bpy.ops.object.join()
                bpy.ops.object.editmode_toggle()
                bpy.ops.mesh.remove_doubles()
                bpy.ops.object.editmode_toggle()
                bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
                bpy.context.object.name = "PixelMakerObject"
            else:
                bpy.ops.object.select_all(action='TOGGLE')    
                bpy.ops.object.select_all(action='TOGGLE')

            bpy.ops.wm.console_toggle()

        def drawCube(x, y, col):
            a = col[3]
            # Checks to see whether or not the pixel is transparent. If not, then it creates a material and creates the cube.
            if a != 0:
                material = makeMaterial(col)            
                bpy.ops.mesh.primitive_cube_add(location=(x - 1, y - 1, 1))
                newObject = bpy.context.active_object
                newObject.data.materials.append(material)

        def makeMaterial(color):
            alpha = 1.0
            red, green, blue, alpha = color
            colorName = "material"

            color = bpy.data.materials.new(colorName)
            color.use_nodes = True
            Diffuse_BSDF = color.node_tree.nodes['Diffuse BSDF']
            Diffuse_BSDF.inputs[0].default_value = [red, green, blue, alpha]
            color.diffuse_color = [red, green, blue]
            return color

        setup(importedImage)

        return {'FINISHED'}    

def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.pixel_file_import = bpy.props.StringProperty(name="Image", description="Name of the image to use in the Pixel Maker add on", default="image.png")
    bpy.types.Scene.pixel_join_cubes = bpy.props.BoolProperty(name="Join Cubes", description="Join the cubes?", default = True)
    bpy.types.Scene.pixel_depth = bpy.props.IntProperty(name="Depth", description="Number of layers", default = 1)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.file_import_pixel

if __name__ == "__main__":
    register()
$\endgroup$
  • $\begingroup$ Can you tell how many different materials there are and how the input looks? $\endgroup$ – CoDEmanX Aug 6 '14 at 20:01
  • $\begingroup$ @CoDEmanX What do you mean? $\endgroup$ – Nathan Craddock Aug 6 '14 at 20:03
  • $\begingroup$ You're asking whether an identical material exists, but did not post any images. So you are the only one who knows that 32x32 input image. Thus, you should actually know how many different colors there are... If they are all different, then you can't reduce the material count (unless you can utilize the object color property). $\endgroup$ – CoDEmanX Aug 6 '14 at 20:10
  • $\begingroup$ @CoDEmanX My image is 32x32 pixels. It is an image of Megaman and it only has 5 colors (not counting transparent). I have over 1000 materials created when I only need 5. Does that help? $\endgroup$ – Nathan Craddock Aug 6 '14 at 20:12
  • $\begingroup$ You get best results here when you post what you've done so far (script, example input, because this would provide a detailed context) , I guess you could create a lookup map (key:rgb, value:material) of already existing materials in order to re-use same the material when they have identical colors. $\endgroup$ – stacker Aug 6 '14 at 20:33
2
$\begingroup$

I modified your script, added a map and reused already created materials:

class pixelMaker(bpy.types.Operator):
    """Run the Pixel Maker Addon"""
    bl_idname = "object.make_pixel"
    bl_label = "Run Pixel Maker"
    bl_options = {'REGISTER', 'UNDO'}
    mat_map = dict() # <---- map

...

def makeMaterial(color):
    key = repr(color)
    if key in self.mat_map:
        return self.mat_map[key]

    alpha = 1.0
    red, green, blue, alpha = color
    colorName = "material"

    material = bpy.data.materials.new(colorName)
    material.use_nodes = True
    Diffuse_BSDF = material.node_tree.nodes['Diffuse BSDF']
    Diffuse_BSDF.inputs[0].default_value = [red, green, blue, alpha]
    material.diffuse_color = [red, green, blue]
    self.mat_map[ key ]=material
    return material
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.