Unlike the pre­vi­ous part, this tuto­r­i­al will be lighter on math and focus more on the "Blender stuff". We will look at adding and apply­ing mod­i­fiers, read­ing a mesh from a file and man­ag­ing com­plex­i­ty.
This time we are doing things dif­fer­ent­ly. We are going to cre­ate a JSON file for the cube mesh data, then build the mesh from it. This way we can eas­i­ly replace the mesh data with any oth­er, with­out hav­ing to change much in the actu­al code.
We won't stop once we have the object done and linked to the scene either. We will trans­form the mesh, set it to smooth, add mod­i­fiers and (option­al­ly) apply them. That's a lot of code, but luck­i­ly it's code that can be reused eas­i­ly. As a bonus, we'll also add ran­dom­ness to the mesh trans­for­ma­tions so we get a dif­fer­ent cube every time the script is run. Final­ly, we'll use a sim­ple for­mu­la to make the bev­el mod­i­fi­er some­what con­sis­tent through dif­fer­ent scales.
Here's the imports. We need the OS pack­age for the path mod­ule.
Before we read the cube data from a file, we need to have a way to con­vert that infor­ma­tion into a mesh and an object linked to the scene. Luck­i­ly we have been doing that since part 1, so let's start by abstract­ing the object mak­ing into it's own func­tion. The first thing to con­sid­er is how we want data to be passed. We could accept a JSON filepath and open it here or even a JSON data string to decode, but these options will lim­it the func­tion to only work with json data. What if we gen­er­at­ed ver­tices by code?
Instead of this, the func­tion will expect a sin­gle dic­tio­nary with three entries (ver­tices, edges, faces). Note that you don't need to declare edges if you have a list of faces, in this case the edges list would be emp­ty (but still needs to exist, oth­er­wise we get a Key­Er­ror).
The val­i­date() func­tion will check the mesh for invalid geom­e­try. By default validate() will only print to the ter­mi­nal if the mesh is invalid. The ver­bose para­me­ter makes it print more infor­ma­tion, even if the mesh is valid. This is more a mat­ter of taste, and if you're shar­ing the script you might want to turn ver­bose off so it doesn't print more stuff than nec­es­sary.
Before get­ting to JSON, let's make anoth­er util­i­ty func­tion. We can grab the smooth­ing code from the pre­vi­ous tuto­r­i­al and sep­a­rate it into it's own func­tion.
The CSV for­mat is intend­ed for tab­u­lar data and sim­ple lists, but what we need to hold in these files are mul­ti­ple nest­ed lists. Each list (verts or faces) con­tains mul­ti­ple lists of val­ues (three or four, respec­tive­ly).
JSON works quite well for this since it maps one to one with Python's data struc­tures. It's also very com­pact (unlike XML) and Python includes a nice pack­age for encoding/decoding these files. Oth­er pos­si­ble options could be YAML and OBJ, but you would need a third par­ty pack­age for those. And of course if you real­ly want to, you can also make your own for­mat.
With that said let's start by cre­at­ing a json file for the cube first.
Before we can read the file, we must be able to get a cor­rect and absolute path for the json file. Since it's saved in the same place as the blend file, we can mix Blender's path util­i­ties with os.path to make a func­tion that makes this path.
""" Return an absolute path for a filename relative to the blend's path """
And obvi­ous­ly you will see a famil­iar cube sit­ting in the mid­dle of the scene.
The process for read­ing mesh data into objects is com­plete, but it's noth­ing fan­cy. Let's bring back matrix trans­for­ma­tions from part 2 to spice things up. But instead of direct­ly apply­ing matri­ces on objects, we will do it through a func­tion. This new func­tion will take care of gen­er­at­ing the matri­ces from more sim­ple para­me­ters and apply them whether we pass a mesh dat­a­block or an object.
Rota­tion as a tuple, where the first val­ue is the rota­tion in degrees and the sec­ond is a string rep­re­sent­ing the axis to rotate. This must be a string that Matrix.Rotation() accepts.
The last three are option­al and if they are omit­ted the matrix will be mul­ti­plied by one (the same as mul­ti­ply­ing by an iden­ti­ty matrix). How do we detect what the first para­me­ter is? We use the EAFP prin­ci­ple ("Eas­i­er to ask for for­give­ness, than per­mis­sion"). We know that objects have a matrix_world prop­er­ty and mesh­es a transform() method. We can try to use the first, and if we fail then try to use the sec­ond. If we still have failed, then the para­me­ter is not trans­formable and we can raise an error.
This excep­tion can be caught high­er in the stack when we call this func­tion lat­er.
""" Apply transformation matrices to an object or mesh """
Now that we got our tools, let's make some­thing with them.
You might have noticed there are no mod­ule-lev­el vari­ables this time. We will put the main code inside a func­tion. There are two good rea­sons to do this: we can catch errors and stop the script with­out exit­ing Blender, and we can reuse and adapt this code (put it in a loop for instance).
""" Make a cube object """
The roundness and subdivisions vari­ables con­trol the bev­el and sub­di­vi­sion mod­i­fiers that we'll add in the next sec­tion. Notice that we are also tak­ing the path to the json file in the new func­tion, that way we can run the entire thing on dif­fer­ent mesh­es.
We can now add some basic error han­dling by call­ing this func­tion inside a try block.
You can print errors, log them or if you're call­ing this from an operator's execute() you can also show a pop­up. The inter­est­ing bit is that since the entire main code is in make_object() we can stop the script any time we find an error. In fact, we can stop the script at any arbi­trary point by sim­ply return­ing from this func­tion. If we had put the code at mod­ule-lev­el instead of a func­tion (like we did in pre­vi­ous parts) we would have no ele­gant way of stop­ping the script. While Python offers sys.exit() and raise SystemExit() to stop exe­cu­tion, these also kill Blender.
There's enough to talk about error han­dling to make an entire sep­a­rate tuto­r­i­al but hope­ful­ly this gives you some ideas.
This is a tuto­r­i­al about ROUNDED cubes isn't it? Let's round them then! You could do this by man­u­al­ly chang­ing the ver­tices' coor­di­nates. Cat­like cod­ing has an excel­lent tuto­r­i­al on this (for Uni­ty). But this is Blender, and we have an artillery of mod­i­fiers at our dis­pos­al. Let's be lazy-smart and use the bev­el mod­i­fi­er to round the cubes.
Adding a mod­i­fi­er is that easy. The first para­me­ter is a name that can be any­thing you want, the sec­ond is the type of mod­i­fi­er. You can find a list of the mod­i­fi­er type strings in the API doc­u­men­ta­tion We can also add some refine­ment to the cube using sub­di­vi­sion. This is makes high­er lev­els of round­ness look bet­ter (at least more spher­i­cal). Sub­di­vi­sions aren't always nec­es­sary though. We can make it option­al by sim­ply mak­ing sure the subdivisions para­me­ter is larg­er than zero.
For anoth­er nice touch, we can apply the mod­i­fiers. There is no auto­mat­ic way of doing this (except call­ing the oper­a­tor). What we have to do is take the derived mesh (the result­ing mesh from all the mod­i­fiers) and replace the actu­al mesh data with it. Then we can remove all mod­i­fiers.
This is some­thing that you might want to do in many places, so let's also make a func­tion for it.
""" Apply all modifiers on an object """
The set­tings type para­me­ter expects one of two strings: 'PREVIEW' and 'RENDER'. These con­trol which mod­i­fiers and which set­tings in the mod­i­fiers is applied, depend­ing on them being enabled for the view­port or ren­der. For instance, the sub­surf mod­i­fi­er has two lev­els (view­port and ren­der). If you want­ed to apply the view­port lev­el you would pass PREVIEW.
There's no sci­en­tif­ic rea­son to use 5 by the way, it just looks good to me. Now that we are chang­ing the scale of the object, wouldn't it be nice if we could also cal­cu­late the bev­el size to keep the round­ness con­sis­tent between all the cubes? This is as sim­ple as divid­ing by the scale aver­age.
Smash the run script but­ton a few times and watch the cubes hap­pen.
That was a lot, but hope­ful­ly you can see how writ­ing mod­u­lar and sim­ple code can help keep com­plex­i­ty under con­trol. Unread­able code is unmain­tain­able code, don't under­sti­mate the impor­tante of keep­ing things clean and mod­u­lar. Remem­ber some­one will have to read and make sense of that code in the future. And that some­one will prob­a­bly be you.
Add some ran­dom­ness to the ver­tices loca­tion before plug­ging them into from_pydata().
Cal­cu­late the amount of seg­ments in the bev­el mod­i­fi­er in rela­tion to the sub­di­vi­sons val­ue, so that the result­ing geom­e­try is more even.
Try mak­ing JSON files for oth­er mesh­es. All you have to do is put verts, edges and faces in a dic­tio­nary, call one of the json encod­ing func­tions and save it to a file.
All the posts you can readEXP 002 // BacteriaBottles Mockup updated!