Add mdbook

This commit is contained in:
Carson Katri 2022-11-15 12:59:28 -05:00
commit 146e83f8ae
56 changed files with 817 additions and 9 deletions

View file

@ -0,0 +1,3 @@
# Advanced Scripting
Now that we've covered the basics, let's take a look at some more advanced scripting techniques.

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -0,0 +1,30 @@
# Generators
Python has support for [generators](https://wiki.python.org/moin/Generators) using the `yield` keyword.
Geometry Script tree functions can be represented as generators to output multiple values. If every generated value is `Geometry`, the values are automatically connected to a *Join Geometry* node and output as a single mesh.
```python
@tree("Primitive Shapes")
def primitive_shapes():
yield cube()
yield uv_sphere()
yield cylinder().mesh
```
![](./geometry_generator.png)
However, if any of the outputs is not `Geometry`, separate sockets are created for each output.
```python
@tree("Primitive Shapes and Integer")
def primitive_shapes():
yield cube()
yield uv_sphere()
yield cylinder().mesh
yield 5 # Not a geometry socket type
```
![](./mixed_generator.png)
> The first output is always displayed when using a *Geometry Nodes* modifier. Ensure it is a `Geometry` socket type, unless you are using the function as a node group.

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -0,0 +1,25 @@
# Node Groups
A Geometry Script can have more than one tree function. Each tree function is a node group, and tree functions can be used in other tree functions to create *Node Group* nodes.
```python
@tree("Instance Grid")
def instance_grid(instance: Geometry):
""" Instance the input geometry on a grid """
return grid().mesh_to_points().instance_on_points(instance=instance)
@tree("Cube Grid")
def cube_grid():
""" Create a grid of cubes """
return instance_grid(instance=cube(size=0.2))
```
The *Cube Grid* tree uses the *Instance Grid* node group by calling the `instance_grid` function:
![](./cube_grid.png)
The *Instance Grid* node group uses the passed in `instance` argument to create a grid of instances:
![](./instance_grid.png)
This concept can scale to complex interconnected node trees, while keeping everything neatly organized in separate functions.

5
book/src/api/basics.md Normal file
View file

@ -0,0 +1,5 @@
# Basics
Creating Geometry Scripts can be as easy or complex as you want for your project.
Throughout this guide, scripts will be displayed alongside the generated nodes to provide context on how a script relates to the underlying nodes.
Setting up an editor for [external editing](../setup/external-editing.md) is recommended when writing scripts, but [internal editing inside Blender](../setup/internal-editing-basics.md) will suffice for the simple examples shown here.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -0,0 +1,38 @@
# Modules
The first step when writing is script is importing the `geometry_script` module. There a are a few ways of doing this:
## Import All Names (Recommended)
This will import every type and function available into your script. It can make it easy to discover what's available with code completion, and makes the scripts more terse.
```python
from geometry_script import *
cube(...) # Available globally
my_geo: Geometry # All types available as well
```
## Import Specific Names
This will import only the specified names from the module:
```python
from geometry_script import cube, Geometry
cube(...) # Available from import
my_geo: Geometry
```
## Namespaced Import
This will import every type and function, and place them behind the namespace. You can use the module name, or provide your own.
```python
import geometry_script
geometry_script.cube(...) # Prefix with the namespace
my_geo: geometry_script.Geometry
```
```python
import geometry_script as gs
gs.cube(...) # Prefix with the custom name
my_geo: gs.Geometry
```
Now that you have Geometry Script imported in some way, let's create a tree.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,124 @@
# Sockets
Because scripts are converted to Geometry Node trees, you typically cannot use default Python types as arguments. In some cases, they will be automatically converted for you, but in general you will be dealing with socket types.
## What is a socket?
A socket is any input or output on a node. Take the *Cube* node for example:
![](./cube_node.png)
This node has 4 input sockets, and 1 output socket.
* Input Sockets
* Size: `Vector`
* Vertices X: `Int`
* Vertices Y: `Int`
* Vertices Z: `Int`
* Output Sockets
* Mesh: `Geometry`
A socket does not represent a value itself. For example, the `Size` socket does not necessarily represent the value `(1, 1, 1)`. Instead, it can be connected to another node as an input, giving it a dynamic value.
When we write scripts, we typically deal with socket types, not concrete values like `(1, 1, 1)`. Take this script for example:
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
return cube(size=size)
```
The `size` argument creates a input socket with the type `Vector`. This is then connected to the `size` socket of the *Cube* node.
![](./cube_tree_size.png)
Our script does not run every time the node tree is evaluated. It only runs once to create the node tree. Therefore, we have no way of knowing what value `size` has when the script runs, because it is dynamic.
## What sockets *can* do
Sockets are great for passing values between nodes. A socket type like `Geometry` does not represent concrete vertices, edges, and faces. Instead, it represents the input or output socket of a node. This lets us use it to create connections between different nodes, by passing the output of one node to the input of another.
## What sockets *cannot* do
Sockets cannot be read for their concrete value. A `Float` socket type does not equal `5` or `10` or `3.14` to our script. It only represents the socket of a node. If you try to `print(...)` a socket, you will receive a generic reference type with no underlying value.
## Why use sockets?
You might be wondering, "if you can't access the value of a socket, what can you do with it?"
Geometry Script provides many helpful additions that make working with sockets about as easy as working with a concrete value.
## Socket Math
Socket types can be used to perform math operations. The proper *Math* node will be created automatically for you, so you can focus on writing a script and not thinking about sockets. If you use `Float` or `Int` it will create a *Math* node, and if you use a `Vector` it will create a *Vector Math* node.
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
doubled = size * (2, 2, 2) # Multiply each component by 2
return cube(size=doubled)
```
![](./cube_tree_size_double.png)
Several common math operations are available, such as:
* Add (`socket + 2`)
* Subtract (`socket - 2`)
* Multiply (`socket * 2`)
* Divide (`socket / 2`)
* Modulo (`socket % 2`)
## Socket Comparison
Socket types can be compared with Python comparison operators. A *Compare* node will be created with the correct inputs and options specified.
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
show_cube = size > (2, 2, 2) # Check if each component is greater than 2
return cube(size=show_cube)
```
![](./cube_tree_size_compare.png)
Several common comparison operators are supported, such as:
* Equal To (`socket == 2`)
* Not Equal To (`socket != 2`)
* Less Than (`socket < 2`)
* Less Than Or Equal To (`socket <= 2`)
* Greater Than (`socket > 2`)
* Greater Than Or Equal To (`socket >= 2`)
## Vector Component Properties
While the `Vector` type does not equate to three concrete components, such as `(1, 2, 3)`, you can still access the `x`, `y`, and `z` components as sockets. A *Separate XYZ* node will be created with the correct inputs and outputs specified.
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
height = size.z # Access the Z component
# Multiply the height by 2 but leave the other components unchanged.
return cube(size=combine_xyz(x=size.x, y=size.y, z=height * 2))
```
For each component access, a *Separate XYZ* node is created.
![](./cube_tree_size_components.png)
## Chained Calls
Any node function can be called on a socket type. This will automatically connect the socket to the first input of the node.
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
return cube(size=size).mesh_to_volume()
```
The output of the *Cube* node (a `Geometry` socket type) is connected to the first input of the *Mesh to Volume* node.
![](./cube_tree_mesh_to_volume.png)
The same script without chaining calls is written more verbosely as:
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
return mesh_to_volume(mesh=cube(size=size))
```

View file

@ -0,0 +1,80 @@
# Tree Functions
Node trees are created by decorating a function with `@tree`. Let's try creating a simple tree function.
> The code samples for the rest of the book assume you are importing all names with `from geometry_script import *`. However, if you are using a namespaced import, simply prefix the functions and types with `geometry_script` or your custom name.
```python
@tree
def cube_tree():
...
```
By default, the name of your function will be used as the name of the generated node tree. However, you can specify a custom name by passing a string to `@tree`:
```python
@tree("Cube Tree")
def cube_tree():
...
```
## Group Output
Every node tree is **required** to return `Geometry` as the first output. Let's try returning a simple cube.
```python
@tree("Cube Tree")
def cube_tree():
return cube()
```
Here we call the `cube(...)` function, which creates a *Cube* node and connects it to the *Group Output*.
![](./cube_tree.png)
You can also return multiple values. However, `Geometry` must always be returned first for a tree to be valid.
```python
@tree("Cube Tree")
def cube_tree():
return cube(), 5
```
![](./cube_tree_int.png)
## Group Input
All arguments in a tree function must be annotated with a valid socket type. These types are provided by Geometry Script, and are not equivalent to Python's built-in types. Let's add a size argument to our Cube Tree.
```python
@tree("Cube Tree")
def cube_tree(size: Vector):
return cube(size=size)
```
This creates a *Size* socket on the *Group Input* node and connects it to our cube.
![](./cube_tree_size.png)
The option is available on the Geometry Nodes modifier.
![](./cube_tree_modifier.png)
The available socket types match those in the UI. Here are some common ones:
* `Geometry`
* `Float`
* `Int`
* `Vector`
> You *cannot* use Python's built-in types in place of these socket types.
In the next chapter, we'll take a closer look at how socket types work, and what you can and cannot do with them.
### Default Values
You can specify a default for any argument, and it will be set on the modifier when added:
```python
@tree("Cube Tree")
def cube_tree(size: Vector = (1, 1, 1)):
return cube(size=size)
```
![](./cube_tree_size_input.png)

View file

@ -0,0 +1,111 @@
# Using Nodes
Node functions are automatically generated for the Blender version you are using. This means every node will be available from geometry script.
This means that when future versions of Blender add new nodes, they will all be available in Geometry Script without updating the add-on.
To see all of the node functions available in your Blender version, open the *Geometry Script* menu in the *Text Editor* and click *Open Documentation*.
![](./open_documentation.png)
This will open the automatically generated docs page with a list of every available node and it's inputs and outputs.
## How nodes are mapped
All nodes are mapped to functions in the same way, so even without the documentation you can decifer what a node will equate to. Using an [IDE with code completion](../../setup/external-editing.md) makes this even easier.
The general process is:
1. Convert the node name to snake case.
2. Add a keyword argument (in snake case) for each property and input.
3. If the node has a single output, return the socket type, otherwise return an object with properties for each output name.
> Properties and inputs are different types of argument. A property is a value that cannot be connected to a socket. These are typically enums (displayed in the UI as a dropdown), with specific string values expected. Check the documentation for a node to see what the possible values are for a property.
Let's take a look at two nodes as an example.
### Cube
![](./cube_node.png)
1. Name: `Cube` -> `cube`
2. Keyword Arguments
* `size: Vector`
* `vertices_x: Int`
* `vertices_y: Int`
* `vertices_z: Int`
3. Return `Geometry`
The node can now be used as a function:
```python
cube() # All arguments are optional
cube(size=(1, 1, 1), vertices_x=3) # Optionally specify keyword arguments
cube_geo: Geometry = cube() # Returns a Geometry socket type
```
The generated documentation will show the signature, result type, and [chain syntax](./sockets.md#chained-calls).
#### Signature
```python
cube(
size: VectorTranslation,
vertices_x: Int,
vertices_y: Int,
vertices_z: Int
)
```
#### Result
```python
mesh: Geometry
```
#### Chain Syntax
```python
size: VectorTranslation = ...
size.cube(...)
```
### Capture Attribute
![](./capture_attribute_node.png)
1. Name `Capture Attribute` -> `capture_attribute`
2. Keyword Arguments
* Properties
* `data_type: Literal['FLOAT', 'INT', 'FLOAT_VECTOR', 'FLOAT_COLOR', 'BYTE_COLOR', 'STRING', 'BOOLEAN', 'FLOAT2', 'INT8']`
* `domain: Literal['POINT', 'EDGE', 'FACE', 'CORNER', 'CURVE', 'INSTANCE']`
* Inputs
* `geometry: Geometry`
* `value: Vector | Float | Color | Bool | Int`
3. Return `{ geometry: Geometry, attribute: Int }`
The node can now be used as a function:
```python
result = capture_attribute(data_type='BOOLEAN', geometry=cube_geo) # Specify a property and an input
result.geometry # Access the geometry
result.attribute # Access the attribute
```
The generated documentation will show the signature, result type, and [chain syntax](./sockets.md#chained-calls).
#### Signature
```python
capture_attribute(
data_type: Literal['FLOAT', 'INT', 'FLOAT_VECTOR', 'FLOAT_COLOR', 'BYTE_COLOR', 'STRING', 'BOOLEAN', 'FLOAT2', 'INT8'],
domain: Literal['POINT', 'EDGE', 'FACE', 'CORNER', 'CURVE', 'INSTANCE'],
geometry: Geometry,
value: Vector | Float | Color | Bool | Int
)
```
#### Result
```python
{ geometry: Geometry, attribute: Int }
```
#### Chain Syntax
```python
geometry: Geometry = ...
geometry.capture_attribute(...)
```