motiv-tree

Übersicht: Python & Blender


Lernziel

a

Was kann modelliert werden? Das wohl häufigste Motiv sind Landschaften mit den folgenden Objekten, die wir zum Teil auch erzeugen wollen:

  • Wolken
  • Steine
  • Bäume
  • Gras
  • Wasser
  • Berge/Landschaft

Handlungsanweisungen

Aufgaben:
  1. Kopiere das Skript für den Wolken-Generator (vorherige Station).
  2. Was ändert sich, wenn aus der Wolke eine Baumkrone und zusätzlich ein kompletter Baum generiert werden soll?
  3. Wie die Baumkrone soll der Stamm unterschiedliche Formen erhalten.
  4. Welche Stammformen gibt es. Schlag in Büchern und auf Websiten nach.
  5. Füge ein oder mehrere Äste zum Stamm hinzu?
  6. Pflanze einen Baum!

Baumkrone und -stamm

Baumkronen sind den Wolken oft sehr ähnlich, deshalb bietet sich die Nutzung der Wolkenklasse an. Neu ist der Baumstamm, dessen Erzeugung hier schrittweise erklärt wird. Wer einen echten Baum generieren will, findet hier eine Erweiterung zu Blender: Naturnahe Bäume als Plugin und auch ein passendes Video zur Benutzung Noch mehr Theorie unter Lindenmayer-System oder L-System

Das Wolkenskript als Vorlage

Mach eine Kopie von dem Script und beginne mit den Umbenennungen.

use-cases/art/low-poly/cloud-e.py (Source)

#!bpy
"""
Name: 'Cloud-Generator'
Blender: 2.7x
Group: 'Low poly'
Tooltip: 'Part of a Low-Poly-Skripts collection'
"""
import bpy
import random

class Cloud():
    """Create a cloud as low poly"""

    def __init__(self):
        pass

    def setColor(self, obj, material, color):
        material.diffuse_color = color
        material.specular_hardness = 200
        obj.data.materials.append(material)

    def new(self, min=0, max=1):
        """ constuction of a new cloud """
        bpy.ops.mesh.primitive_ico_sphere_add(location=(0, 0, 0))
        obj = bpy.context.object
        obj.name = "cloud"
        obj.location.x = int(random.uniform(min +10 * -1, max + 10))
        obj.location.y = int(random.uniform(min, max)) * 10
        obj.location.z = int(random.uniform(min, max +3 )) * 10

        obj.scale[0] = int(random.uniform(min + 1, max + 3))
        obj.scale[1] = int(random.uniform(min + 1, max + 3))
        obj.scale[2] = int(random.uniform(min + 1, max + 4))

        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.subdivide()
        bpy.ops.mesh.subdivide()
        bpy.ops.object.editmode_toggle()

        bpy.ops.object.modifier_add(type='DISPLACE')
        STUCCI_TEX = bpy.data.textures.new("Stucci tex", type="STUCCI")
        obj.modifiers["Displace"].texture = STUCCI_TEX
        obj.modifiers["Displace"].strength = .2

        MATERIAL_CLOUD = bpy.data.materials.new('cloud')
        self.setColor(obj,
                      MATERIAL_CLOUD,
                      (random.uniform(0, .3),
                       random.uniform(0, .3),
                       random.uniform(0.3, 1)))

    def remove(self, name=None):
        """ Delete a cloud or all"""

        if name:
            bpy.ops.object.select_pattern(pattern=name)
        else:
            bpy.ops.object.select_pattern(pattern="cloud*")

        bpy.ops.object.delete()


if __name__ == "__main__":

    # switch to object mode, if nessasary
    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='OBJECT')
    cloud = Cloud()
    cloud.remove()
    for i in range(5):
        cloud = Cloud()
        cloud.new()

Der Stamm

Der Stamm soll nicht gerade wachsen und auch noch das Potenzial haben, in einer folgenden Erweiterung Äste zu erhalten. Ersteres wird hier beschrieben. Die Lösung für das Ast-Problem bleibt dem geneigten Leser und Programmierer überlassen.

Ein Beispiel mit drei Exemplaren für die Zielstellung:

/use-cases/art/low-poly/trees.png

Der Stamm entsteht am Null-Punkt des Koordinatensystems. Wie schon so oft, übergeben wir das aktuelle Objekt an eine Variable obj und vergeben auch einen Namen.

use-cases/art/low-poly/tree.py (Source)

    def tree_trank(self):
        """ Constuction of a new tree top """

        # we are starting with a circle
        bpy.ops.mesh.primitive_circle_add(vertices=8,
                                          radius=0.1,
                                          location=(0, 0, 0))
        obj = bpy.context.object
        obj.name = "tree_trank"

Nachdem der erste Kreis platziert worden ist extrudieren wir den Kreis (genau fünf mal). Dabei führen wir ein wenig Protokoll, denn wir wissen nicht wo der Stamm endet. Wir halten also mit jeder Wiederholung die aktuelle Koordinate für x und auch die aktuelle Höhe des Stammes fest. Damit wir sie später wieder benutzen können, speichern wir die Werte in den passenden Instanzvariablen, die wir nun in der __init__-Methode schon einmal auf den Wert 0 setzen:

Die erweiterte __init__-Methode

use-cases/art/low-poly/tree.py (Source)

    def __init__(self):
        self.top = 0
        self.x = 0

Extrudieren im Edit-Mode

Nur im Edit-Mode kann extrudiert werden. Wieder verwenden wir zufällige Werte, um in einem vorgegebenen Werterbereich eine Verschiebung in der x-Achse als auch das Wachsen in die Höhe variieren zu können. Damit sollte kein Stamm dem anderen gleichen.

use-cases/art/low-poly/tree.py (Source)

        # switch to EDIT mode
        bpy.ops.object.mode_set(mode='EDIT')
        # start extruding the circle
        for i in range(5):
            length = random.uniform(0, .5)
            self.top = self.top + length
            bpy.ops.mesh.extrude_region_move(
                MESH_OT_extrude_region={"mirror": False},
                TRANSFORM_OT_translate={"value": (0,
                                                  0,
                                                  length)})
            new_x = random.uniform(.1, .2) * random.choice([1, -1])
            self.x = self.x + new_x
            bpy.ops.transform.translate(value=((new_x,
                                                0,
                                                0)))

Den Stamm verbiegen

Natürlich wird der Stamm noch ein wenig verbogen und erhält auch einen Farbanstrich.

use-cases/art/low-poly/tree.py (Source)

        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.modifier_add(type='DECIMATE')
        obj.modifiers["Decimate"].ratio = 0.114

        MATERIAL_TREE_TRANK = bpy.data.materials.new('Tree trank')

        self.setColor(obj,
                      MATERIAL_TREE_TRANK, (random.uniform(0, .3),
                                            random.uniform(0, .3),
                                            0))

Den Baum zusammensetzen

Die Einzelteile des Baumes können nun schon, jedes Teil für sich, erzeugt werden. Damit die Einzelteile auch zusammenhalten. werden sie in der Methode new für die Ewigkeit verbunden.

use-cases/art/low-poly/tree.py (Source)

    def new(self):

        # get the context
        scn = bpy.context.scene

        parts = ['tree_trank', 'tree_top', 'tree']

        self.tree_trank()
        self.tree_top()
        obj = bpy.context.object
        scn.objects[parts[0]].select = True
        scn.objects[parts[1]].select = True
        bpy.ops.object.join()
        obj.name = parts[2]

Die Methode trunk und new komplett

use-cases/art/low-poly/tree.py (Source)

    def tree_trank(self):
        """ Constuction of a new tree top """

        # we are starting with a circle
        bpy.ops.mesh.primitive_circle_add(vertices=8,
                                          radius=0.1,
                                          location=(0, 0, 0))
        obj = bpy.context.object
        obj.name = "tree_trank"

        # switch to EDIT mode
        bpy.ops.object.mode_set(mode='EDIT')
        # start extruding the circle
        for i in range(5):
            length = random.uniform(0, .5)
            self.top = self.top + length
            bpy.ops.mesh.extrude_region_move(
                MESH_OT_extrude_region={"mirror": False},
                TRANSFORM_OT_translate={"value": (0,
                                                  0,
                                                  length)})
            new_x = random.uniform(.1, .2) * random.choice([1, -1])
            self.x = self.x + new_x
            bpy.ops.transform.translate(value=((new_x,
                                                0,
                                                0)))

        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.modifier_add(type='DECIMATE')
        obj.modifiers["Decimate"].ratio = 0.114

        MATERIAL_TREE_TRANK = bpy.data.materials.new('Tree trank')

        self.setColor(obj,
                      MATERIAL_TREE_TRANK, (random.uniform(0, .3),
                                            random.uniform(0, .3),
                                            0))

    def new(self):

        # get the context
        scn = bpy.context.scene

        parts = ['tree_trank', 'tree_top', 'tree']

        self.tree_trank()
        self.tree_top()
        obj = bpy.context.object
        scn.objects[parts[0]].select = True
        scn.objects[parts[1]].select = True
        bpy.ops.object.join()
        obj.name = parts[2]

Ein Nachtrag

Wer die Konsole aufmerksam beobachtet, hat die folgenden Warnungen bemerkt:

convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context
convertViewVec: called in an invalid context

Diese Meldung kann man in unserem Fall ignorieren, oder wie in einem Forenbeitrag vorgeschlagen, ausschalten. Besser ist es wohl, nach einer Alternative Ausschau zu halten. Und diese Alternative heißt BMesh.

Tranformation über ein bmesh

Das BMesh ist ein neuer Datentyp und enthält die Struktur eines Körpers. Ein Beispiel für die Neuanlage findet man in der Station Klassen: eine Pyramide

Diesmal ist das Objekt aber schon erzeugt und soll nun nachträglich verändert werden.

Dazu sind drei Schritte notwendig:

  1. konvertiere das Objekt in ein bmesh
  2. manipuliere die Koordinaten
  3. speichere die neuen Koordinaten

zu Schritt 1 und 3

Vorausgesetzt, es ist schon etwas markiert und der Edit-Mode ist aktiviert.

bm = bmesh.from_edit_mesh(obj.data)
# Hier dann weitere Manipulationen ...
bmesh.update_edit_mesh(obj.data)
/use-cases/art/low-poly/tree2.jpg
Hilfe: Das Script funktionert, wie man sehen kann, doch ist es noch nicht vollendet! Hilfe ist willkommen.

Kommentare