Sapling: A Tree Generator Add-On for Blender (OSX Fix 10 Aug 2011)

Introduction
I have been working on implementing the tree model described by Jason Weber and Joseph Penn into Blender. This model has proven popular and is used in the tree generator Arbaro and the Blender 2.49 plugin Gen3.

Download Location
http://projects.blender.org/tracker/download.php/153/469/27226/17302/add_curve_sapling_0_2_4_OSX_Fixed.zip

Installation Instructions

  • Download the above file and unzip it into your Blender Add-Ons folder (…/blender/2.58/scripts/addons or some such)
  • Start Blender and go to FILE -> USER PREFERENCES, in the window that opens click on the Add-Ons tab.
  • Click Add Curve in the list on the left.
  • Check the box next to the ‘Sapling’ add-on.
  • Now in the 3D view, go to Add -> Curve -> Add Tree
  • A tree is added and the options are in the tool shelf (T-KEY)
  • Have fun playing around with the options!

Wiki
http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Curve/Sapling_Tree

Tracker
http://projects.blender.org/tracker/index.php?func=detail&aid=27226&group_id=153&atid=469

Version 0.2.4

  • Added rectangular leaves with UV mapping
  • Fixed tracker URL


Version 0.2.3

  • Added wiki and tracker URLs
  • Fixed usage of Matrix.Rotation

Version 0.2.2

  • Fixed preset path to work with user defined script location (code thanks to Sanne).
  • Fixed limit import to work correctly and not export limit import value.
  • Fixed radius calculation issue to correctly implement the weeping willow preset.
  • Fixed presets to have all parameters correct in the preset file.
  • Minor code improvements.

Version 0.2.1

  • Updated to 2.58 API
  • Added the ability to start the tree at an angle to the ground via startCurv
  • Fixed an issue where, even if the seed value was the same, the tree generated would be different
  • Fixed calculation of upward attraction for correct implementation
  • Added presets for CA Black Oak and Weeping Willow.

Version 0.2
Version 0.2 of Sapling has now been uploaded and can be found here. The following changes have been made to this version:

  • Automatic armatures have been added so it is possible to generate an armature and bones for the whole tree easily. This includes the leaves which also have vertex groups to make sure they stay attached to their branches.
  • Automatic armature animations have also been included to add wind to a tree automatically.
  • GUI updated to make it smaller, a drop down box within the panel now allows access to each section of tree parameters. They are in the recommended order of editting.
  • PruneRatio has been added which allows for adjusting the amount of pruning applied.
  • A better visualisation of the pruning envelope has been added to make it easier to see the shape that you are forcing the tree to take on.
  • Leaf generation has had some corrections which should also speed it up.
  • Comments on the code have been added for those interested.

Cheers,
Truman

Version 0.1
Hi All,

I have been working on implementing the tree model described by Jason Weber and Joseph Penn into Blender. This model has proven popular and is used in the tree generator Arbaro and the Blender 2.49 plugin Gen3.

I am happy to announce that ‘Sapling’ is ready for a first release (v0.1) and can be found here. This was written using Blender 2.56.6 r36007 (RC2).

In order to use the script follow these steps:

  • Download the above file and place it in your Blender Add-Ons folder (…/blender/2.56/scripts/addons or some such)
  • Start Blender and go to FILE -> USER PREFERENCES, in the window that opens click on the Add-Ons tab.
  • Click Add Curve in the list on the left.
  • Check the box next to the ‘Sapling’ add-on.
  • Now in the 3D view, go to Add -> Curve -> Add Tree
  • A tree is added and the options are in the tool shelf (T-KEY)
  • Have fun playing around with the options!

There are a few things to note:

  • You can adjust the quality of the tree at any time using the ‘Preview U’ and ‘Bevel Resolution’ option in the Object Data section of the properties panel.

  • The following options are disabled by default to speed up editing:
    [LIST]

  • You must tick the ‘Bevel’ box to see the actual tree.

  • You must tick the ‘Leaves’ box to generate the leaves.

  • You can change ‘Levels’ to see only the level you are editing (and the ones below it).

  • You must tick the box ‘Prune’ if you want to enforce a shape on the tree.

  • The tree which is created by default is the ‘Quaking Aspen’.
    [/LIST]
    Also, there are some elements of the model which are not implemented:

  • Flaring of the trunk is not yet supported, I am currently looking into this.

  • Periodic tapering of the branches is not supported and is unlikely to be due to limitations of using curves for the branches.

  • Wind sway is not yet implemented but is the next item on the to-do list.

The default output of the script, with bevelling and leaves enabled (plus some quick materials) is shown below:



This tree has about 180k verts but as described above, this can be adjusted.

If there is enough interest I might create a tutorial explaining all the options and how to use them.

Please feel free to leave any comments and suggestions as well as bugs to I can try to get them out of the way quickly.

Cheers and enjoy,
Truman

Wow , nice to use ;-)!!!
Question: what should one do to make the leaves be fixed to the twigs and move in the wind?

Attachments


Looks great!

Some very nice additions to the previous script. I like the split feature a lot. Pruning is nice as well.

It did not crash at all. The only unexpected operation that happened was when I typed in a number in one of the fields then pressed CTRL-Z (to undo) the entire panel disappeared as well as the tree I had spent much time on. That is probably more of a Blender panel issue, however. But if you have anyway to deny the panel exit if a CTRL-Z is pressed it might be nice to add.

Attachments


Very cool script… ground my win32 shitbox to a halt with too many branches and leaves.

I have a suggestion for setting up your properties. In an addon I’m grinding away on i started using xml data to store the default settings that the user changes in an operator and preserve these settings for the lifetime of the blend file or to disk.

I think this method or something similar has application here giving an easy way to save your settings as presets like “Oak Tree” or “Rasperry Bush” etc. Saving them to a file makes them available to your next file. The xml data could be served from/to a webserver with a collaborative database. Or easilly added via the text editor.

The snippets come from a multiple script addon in a directory “MocapMadness” . init.py takes care of the importing and calling the register method of each script.

In the config.py script the settings are loaded from settings.xml.

a snippet from config.py


def LoadSettings():
    import os
    Dir = os.path.dirname(__file__)
    path = os.path.join(Dir, 'settings.xml')
    # load settings from the addon. (can be user saved)
    settings = ET.parse(path)
    #print(ET.tostring(settings))
    return settings

settings = LoadSettings()
# Set operator settings from settings node
#


def SetOperatorProperties(name):

    global settings
    dprint("SET OPERATOR PROPERTIES: %s"%name)
    if not settings :
        return(False)

    cmds = []
    Settings = settings.findall('Settings[@name="%s"]/Setting'%name)
    
    for Setting in Settings:

        attrs = ["name","description"]
    
        default = False # Take out default value code.. settings 
        '''
        It's set by settings anyhow.. enables values like
        bpy.context.scene.fps
        to be used as default
        '''
        proptype = Setting.get("type")
        prop_name = Setting.get("prop_name")
        args = []
        
        if proptype in ["int","float"]:
            if default:
               def_value = eval("%s"%Setting.text)
            attrs.extend(["min","max","soft_min","soft_max","unit"])
        elif proptype in ["str"]:
            if default:
                def_value = '"%s"'%Setting.text
            attrs.extend(["maxlen"])
            proptype = "string"
           
        for attr in attrs :
            
            if Setting.get(attr):
                if attr == "default":
                    args.append('%s=%s'%(attr,def_value))
                elif attr in ["description","name"]:
                    args.append('%s="%s"'%(attr,Setting.get(attr)))
                else:
                    args.append('%s=%s'%(attr,Setting.get(attr)))

        expr = '%s =  %sProperty('%(prop_name,str.title(proptype))
        for arg in args:
            expr += '%s,'%arg
        expr += ")"
        cmds.append(expr)
    return cmds

def SetOperatorSettings(operator):
    name = operator.mocapmadness_name
    global settings
    print("GetSettings: %s"%name)
    if not settings :
        return(False)


    Settings = settings.findall('Settings[@name="%s"]/Setting'%name)
    for Setting in Settings:
        print("SETTING: %s"%Setting.text)
        if Setting.get("type") == "str":
            value = Setting.text
        else:    
            value = eval("%s"%Setting.text)
        expr = 'operator.%s =  value'%(Setting.get("prop_name"))
        exec(expr)
            
def PreserveSettings(operator):
    name = operator.mocapmadness_name
    global settings
    print("Preserve Settings:: %s"%name)
    if not settings :
        print("NO SETTINGS")
        return(False)


    Settings = settings.findall('Settings[@name="%s"]/Setting'%name)
    for Setting in Settings:

        value = eval('operator.%s'%Setting.get("prop_name"))
        if Setting.get("type") == "float":
            value = str(round(value,2))
        Setting.text = value
            

Now in the operator.py script the operator class. The methods above are called to generate the operator’s properties and are set, and saved (to file if desired) in the invoke and execute methods.
I haven’t been using vector properties, the code for them needs to be set up.


import MocapMadness.config as mocapmadness
class LoadBvhButton(bpy.types.Operator, ImportHelper):
    bl_idname = "mocapmadness.import_bvh"
    bl_label = "Load BVH file (.bvh)"
    mocapmadness_name = bl_idname
    for cmd in mocapmadness.SetOperatorProperties("mocapmadness.import_bvh"):
        exec(cmd)
    filename_ext = ".bvh"
    filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
    rot_mode = EnumProperty(items=(
        ('QUATERNION', "Quaternion", "Convert rotations to quaternions"),
        ('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"),
        ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
        ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
        ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
        ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
        ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
        ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
        ),
            name="Rotation",
            description="Rotation conversion.",
            default='NATIVE')

    def invoke(self, context, event):
        mocapmadness.SetOperatorSettings(self)   
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'} 

    def execute(self, context):
        ... do stuff
        mocapmadness.Save()
        mocapmadness.PreserveSettings(self)

The data is set up like this and holds the default value of your properties. You use expressions from the math lib and blender props like bpy.context.scene.render.fps.

settings.xml


<Settings>
    <Settings name="mocapmadness.import_bvh">
        <Setting prop_name="Scale" type="float" description="Scale the bvh">0.12</Setting>
        <Setting prop_name="Rot90" type="bool" name="Rotate 90 (Z up)">True</Setting>
        <Setting prop_name="Sync" type="bool" name="Use timing from bvh">True</Setting>
        <Setting prop_name="Origin" type="bool" name="Travel Origin" description="Make the starting position (0,0) and original hip height" >True</Setting>
        <Setting prop_name="TPose" type="bool" name="TPose Rest Pose" description="Attempt to change rest pose to TPose in frame 0"   >True</Setting>
        <Setting prop_name="Discard" type="bool" name="Action Only" description="Keep the action but discard rig and armature">False</Setting>
        <Setting prop_name="UseOnlyRootBoneLoc" type="bool" name="Only rootbone locations" description="Only calculate locations for root bones.">False</Setting> 
        <Setting prop_name="Dismiss" type="bool" name="Dismiss Dialog" description="Return to Import View to Import another.">True</Setting> 
        <Setting prop_name="TargetRig" type="str" name="TargetRig" description="Name of Target Rig">ST_Rig</Setting>
    </Settings>
        <Setting prop_name="fps" type="int" name="Frames_Per_Second" description="Frame Rate">bpy.context.scene.render.fps</Setting>
</Settings>

batFINGER: This is exactly what I would like to do, but up until now didn’t know how to. It’ll take some work to get this set up so it might not be introduced for a while but it is now on my to-do list. Thanks for the great examples.

On a general note, does anyone know how (or even if it is possible) to do the following things:

  • Make sections of script’s UI collapsable so they don’t take up so much room in the tool shelf.
  • Add edit bones to an armature without using bpy.ops.

Cheers,
Truman

PS I have created the animation armature for the tree (using bpy.ops) and corrected the code for the leaves which should also speed up the script. Once the animation is added and a few other things tidied up, I’ll release v0.2

thanks Im subscribing to this thread for later updates :smiley:

Make sections of script’s UI collapsable so they don’t take up so much room in the tool shelf.

There’s a ‘default collapse’ or something like that so it doesn’t show the full panel, just search the ui directory for one that is collapsed by default.

Add edit bones to an armature without using bpy.ops.

arm.edit_bones.new(‘bone’)

But the armature needs to be in editmode and if you don’t set the head/tail blender will delete your zero-length bone (IIRC).

On the collapsible UI. If it was a panel class you could do something like this http://www.blender.org/documentation/250PythonDoc/bpy.types.Panel.html?highlight=panel#mix-in-classes

But since your panel relies on the draw of the operator it might pay to set up a buttons for tabs kinda thing using some dummy display properties and display for that selection in the draw method. In the snippet below your “Branch Growth” box will only show once bevel is True.


        if self.bevel:
            box = layout.box()
            box.label('Branch Growth')
            row = box.row()
            row.prop(self,'levels')
            row = box.row()
            row.prop(self,'attractUp')
            row = box.row()
            col = row.column()
            col.prop(self,'length')
            col = row.column()
            col.prop(self,'lengthV')
            row = box.row()
            col = row.column()
            col.prop(self,'curve')
            col = row.column()
            col.prop(self,'curveV')
            row = box.row()
            col = row.column()
            col.prop(self,'curveBack')
            col = row.column()
            col.prop(self,'taper')
            row = box.row()
            col = row.column()
            col.prop(self,'curveRes')

Thanks Uncle, I’ll look into the panels. With respect to the edit bones, my question was phrased poorly. I have used the method you describe, but unfortunately it requires bpy.ops to enter/exit edit mode, that was the main problem, not actually adding the bones. I have a suspicion there is no way to add bones without this enter/exit op.

Good idea this one, I’ve used an EnumProperty to drive the visibility instead of a boolean to it works like it want it to. Thanks for the idea.

cool job so far
thanks. :slight_smile:

Yep, I think that’s the only way it works due to the way blender stores armatures internally (it creates edit bones when going into edit mode and destroys them when leaving).

Hi All,

Here is a quick update on what I have been working on.

This animation is completely handled by the script using only fcurve modifiers. No keyframing required! The parenting, vertex groups and armature modifiers are also all automatic.

I’m currently trying to clean up the code and add more useful comments. A new version including all these features should be available soon.

Cheers,
Truman

very cool script!
I was testing it out the other day.
thanks, I look forward to the next version. :slight_smile:

awesome, but need a second motion for the leafs.

Great work!

hi,
It would be good to add this to Blender contrib scripts & svn.
If you read this: http://wiki.blender.org/index.php/Dev:Py/Sharing
You can start the proccess easily with a Projects & Wiki page & we can provide you with a good developement enviroment.
I can provide any help you need setting this up.
Thanks, what a cool script.

Version 0.2 has been uploaded, see the first post for the link and a list of the updates. Thanks for the support too!

Hi Meta,

This would be great! I’ll have a read through and get back to you with any questions.

Cheers,
Truman

Hi All,

Here is a tentative update for the tree generator. I have been working on allowing saving and loading of presets and thus needed to move to a multi-script add-on. The zipped files can be found here. To install, unzip into the addons directory of your Blender distribution. Then go into User Preferences and enable ‘Sapling’.

I won’t update the main post yet as I would like to get any feedback that anyone has before making a new release. I will also look into putting up a wiki page for this.

Cheers,
Truman

PS The preset options are available at the top of the panel. Two presets are included, the Quaking Aspen and the Black Tupelo. Enjoy!

Great improvement (see picture, generated by your script)

Small problem?
File “C:\Users\Peter\25Blender\blenderLatest\2.57\scripts\addons\add_curve_sapling\utils.py”, line 238, in growSpline
angleX = stem.splitAngle(splitAng,splitAngV) + stem.curv + uniform(-stem.curvV,stem.curvV) - curveUpAng
UnboundLocalError: local variable ‘curveUpAng’ referenced before assignment

http://petergragert.info/images/Blender/GIF/bladereninWind01.gif

As it is spring, here in Ohio, I see lots of trees moving in the wind and I do have to agree with bat3a. A secondary movement on the leaves would be a nice addition.