3
$\begingroup$

What is this program?

For starters, here's the raw code:

#Cycles Mineways setup
#Version 0.02, 8/13/15


#CONSTANTS

#If this is true, the script will request a scene to work with; otherwise, it will use all scenes
USER_INPUT_SCENE=False
#Cloud state, either True or False
CLOUD_STATE=False
#Changing the number here changes what water shader will be used, 0 to use the normal shader
WATER_SHADER_TYPE=int(0)
#Changine the number here changes the type of sky shader used, 0 for no shader
SKY_SHADER_TYPE=int(0)
#Time of day
TIME_OF_DAY=float(12.00)

#List of transparent blocks
transparentBlocks=["Acacia/Dark_Oak_Leaves","Activator_Rail","Bed","Brewing_Stand","Brown_Mushroom","Cactus","Carrots","Cauldron","Cobweb","Crops","Dandelion","Dead_Bush","Detector_Rail","Enchantment_Table","Glass","Glass_Pane","Grass","Iron_Bars","Iron_Door","Iron_Trapdoor","Large_Flower","Leaves","Lilly_Pad","Melon_Stem","Monster_Spawner","Nether_Wart","Poppy","Potatoes","Pumpkin_Stem","Rail","Red_Mushroom","Redstone_Comparator_(off)","Redstone_Torch_(off)","Repeater_(off)","Sapling","Sign_Post","Sugar_Cane","Tall_Grass","Trapdoor","Vines","Wall_Sign","Wooden_Door"]
#List of light emitting blocks
lightBlocks=["End_Portal","Fire","Redstone_Comparator_(on)","Redstone_Lamp_(on)","Redstone_Torch_(on)","Repeater_(on)","Glowstone","Stationary_Lava","Torch"]


#SHADERS
#Shaders assume that diffuse and output nodes already exist

def Normal_Shader(material):
    material.use_nodes=True
    #add an image texture node
    node_tree=bpy.data.materials[material].node_tree
    node=node_tree.nodes.new('ShaderNodeTexImage')
    node.location=(-300,300)

def Transparent_Shader(material):
    material.use_nodes=True

def Light_Emiting_Shader(material):
    material.use_nodes=True

def Stationary_Water_Shader(material):
    material.use_nodes=True

def Flowing_Water_Shader(material):
    material.use_nodes=True

def Slime_Shader(material):
    material.use_nodes=True

def Ice_Shader(material):
    material.use_nodes=True

def Stained_Glass_Shader(material):
    material.use_nodes=True

def Sky_Shader():
    pass

def Sun_Shader():
    pass


#MAIN

def main():

    print("Started")


    #importing the Blender Python library
    import bpy
    print("Libraries imported")


    #packing all the files into one .blend 
    bpy.ops.file.pack_all()
    print("Files packed")


    #Setting the render engine to Cycles
    if USER_INPUT_SCENE==True:
        #get the scene to use
        scene=input("Please enter the scene you would like to use (default \"Scene\"): ")
        if scene=="":
            scene = "Scene"
        #Set the render engine to Cycles
        bpy.data.scenes[scene].render.engine='CYCLES'
    else:
        for scene in bpy.data.scenes:
            scene.render.engine = 'CYCLES'
    print("Render engine set to Cycles")


    #for every material
    for material in bpy.data.materials:
        #to ignore KeyError s
        try:
            #print that the material is now being worked on
            print("Started "+str(material))
            #if the material is transparent use a special shader
            if any(material==bpy.data.materials[transparentBlock] for transparentBlock in transparentBlocks):
                pass
            #if the material is a light emmitting block use a special shader
            elif any(material==bpy.data.materials[lightBlock] for lightBlock in lightBlocks):
                pass
            #if the material is stationary water, use a special shader
            elif material==bpy.data.materials["Stationary_Water"]:
                pass
            #if the material is flowing water, use a special shader
            elif material==bpy.data.materials["Flowing_Water"]:
                pass
            #if the material is slime, use a special shader
            elif material==bpy.data.materials["Slime"]:
                pass
            #if the material is ice, use a special shader
            elif material==bpy.data.materials["Ice"]:
                pass
            #if the material is stained glass, use a special shader
            elif material==bpy.data.materials["Stained_Glass"] or material==bpy.data.materials["Stained_Glass_Pane"]:
                pass
            #else use a normal shader
            else:
                Normal_Shader(material)
            #print the material has finished
            print("Finished "+str(material))
        except KeyError:
            pass

    #Set up the sky
    print("Started shading sky")
    Sky_Shader()
    print("Sky shaded")


    #Set up the sun
    print("Started shading sun")
    Sun_Shader()
    print("Sun shaded")

main()

In effect, this program for Cycles runs through and sets up materials in the scene using nodes–no biggie. It's very simple, and very incomplete–at this stage, it doesn't even really set up the nodes, let alone link them. However, I've encountered a problem I'm not really sure how to fix.


What's the problem?

We're going to be looking at lines 96-128 in the main.

for material in bpy.data.materials:
        #to ignore KeyError s
        try:
            #print that the material is now being worked on
            print("Started "+str(material))
            #if the material is transparent use a special shader
            if any(material==bpy.data.materials[transparentBlock] for transparentBlock in transparentBlocks):
                pass
            #if the material is a light emmitting block use a special shader
            elif any(material==bpy.data.materials[lightBlock] for lightBlock in lightBlocks):
                pass
            #if the material is stationary water, use a special shader
            elif material==bpy.data.materials["Stationary_Water"]:
                pass
            #if the material is flowing water, use a special shader
            elif material==bpy.data.materials["Flowing_Water"]:
                pass
            #if the material is slime, use a special shader
            elif material==bpy.data.materials["Slime"]:
                pass
            #if the material is ice, use a special shader
            elif material==bpy.data.materials["Ice"]:
                pass
            #if the material is stained glass, use a special shader
            elif material==bpy.data.materials["Stained_Glass"] or material==bpy.data.materials["Stained_Glass_Pane"]:
                pass
            #else use a normal shader
            else:
                Normal_Shader(material)
            #print the material has finished
            print("Finished "+str(material))
        except KeyError:
            pass

This in effect scans to see what shader it should use for the given material it's working on, then executes the respective shader for that material. However, there's a bit of a problem I ran into.

Since my program scans to see what material it's working on, because of the way I set it up, has to assume that it exists before it can check to see if it's working on it, it will return KeyErrors commonly since it's often that the material does not exist.

Example:

if any(material==bpy.data.materials[transparentBlock] for transparentBlock in transparentBlocks):

In this statement, variable material is equal to a material in the scene–take for example, "Dirt". We want to evaluate if "Dirt" is the list transparentBlock. The first material in this list is "Acacia/Dark_Oak_Leaves". However, that material doesn't exist in the scene. Therefore, when bpy.data.materials["Acacia/Dark_Oak_Leaves"] executes, it doesn't work since it can't pull in data for a material that doesn't exist. Therefore it returns the following error:

KeyError: 'bpy_prop_collection[key]: key "Acacia/Dark_Oak_Leaves" not found'

My first instinct was to use a try/except to ignore KeyErrors (as can be seen in the code). However, I can't seem to find a placement for the try/except that will work, since the error immediately interrupts the flow of the program instead of just continuing on. Therefore, it can't evaluate "Dirt" against the rest of the materials in transparentBlocks, or for that matter, any of the other conditionals.


Some ideas

I did come up with some ideas for possible solutions–however, all are rather clumsy.

The first minor way to help is to put the try/except before each if statement. This solves the problem of it not evaluating the other if statements when a KeyError happens; however, it still cancels that if statement.

The first major idea I had would be to scan and see if the materials existed before the if statements; if they didn't, I would simply wipe them from my list (in the case of the transparentBlocks and lightBlocks) or set a variable so as to ignore them later (in the case of each of the if statements). However, this solution is rather clumsy.

The second major idea I had was to simply create materials for every block where an if statement would apply, therefore the keyError wouldn't happen. However, this results in the program working a ton extra for no good reason, setting up materials that never get used, and resulting in a ton of excess materials lying around after the fact.

In essence, I couldn't find a good solution. Are there any better solutions? Should I use one of mine and deal with the clumsy?


Final Thoughts

I know this question is rather specific, but I created a Meta post which seemed to say it would be fine, so I've gone ahead and created this post. If it's not appropriate for this StackExchange, please let me know–also, if you can, please recommend some other places that might be good (StackOverflow? I don't have a lot of experience there, another reason why I'm asking for help).

If you'd like to take a closer look, here's a link (can't use Blend-Exchange, it's too big):

$\endgroup$
  • 1
    $\begingroup$ First of all use bpy.data.materials.get(somename) , it returns None if the material isn't present, else it will return the material. See how this changes your code. $\endgroup$ – zeffii Aug 15 '15 at 6:48
  • $\begingroup$ additionally, I think this is a functional equivalent of the code you highlighted. gist.github.com/zeffii/49a48a7f8999b6e66336 $\endgroup$ – zeffii Aug 15 '15 at 7:06
2
$\begingroup$

I don't fully understand the main goal of the main loop for material in bpy.data.materials loop, but an equivalent code is the following.

# transparentBlocks = already defined earlier
# lightBlocks = already defined earlier

misc_materials = [
    "Stationary_Water", 
    "Flowing_Water", 
    "Slime", 
    "Ice", 
    "Stained_Glass", 
    "Stained_Glass_Pane"
]

for material in bpy.data.materials:
    print("Started "+str(material))

    if material.name in set(transparentBlocks + lightBlocks + misc_materials):
        pass
    else:
        # use a normal shader
        Normal_Shader(material)

    print("Finished "+str(material))
|improve this answer|||||
$\endgroup$
  • $\begingroup$ Heh. Thanks for then edit, but I'm afraid it's no necessary. The "pass" commands are just temporary placeholders, and the actual shaders will be added in later-I just haven't written them yet. Hopefully that explains the purpose of the main. $\endgroup$ – JMY1000 Aug 15 '15 at 20:06
  • $\begingroup$ I'm on mobile so I can't add line breaks; apologies for the multiple comments. I'll give .name a shot later and get back to you. $\endgroup$ – JMY1000 Aug 15 '15 at 20:07
  • $\begingroup$ @JMY1000 hopefully you understand it a bit better now $\endgroup$ – zeffii Aug 15 '15 at 20:09

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.