Add mdbook
12
README.md
|
@ -1,4 +1,4 @@
|
|||
![Geometry Script wordmark](resources/wordmark.png)
|
||||
![Geometry Script wordmark](book/src/images/wordmark.png)
|
||||
|
||||
A scripting API for Blender's Geometry Nodes:
|
||||
|
||||
|
@ -23,7 +23,7 @@ def repeat_grid(geometry: Geometry, width: Int, height: Int):
|
|||
</td>
|
||||
<td>
|
||||
|
||||
![Generated node tree](resources/example_generated_tree.png)
|
||||
![Generated node tree](book/src/images/example_generated_tree.png)
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -143,18 +143,18 @@ To open an external Python file:
|
|||
1. Select the open icon in Blender's Text Editor
|
||||
2. Navigate to the file, then open the sidebar (click the gear icon or press *N*) and uncheck *Make Internal*
|
||||
|
||||
![A screenshot of Blender's file selector with 'Make Internal' unchecked](resources/open_file.png)
|
||||
![A screenshot of Blender's file selector with 'Make Internal' unchecked](book/src/images/open_file.png)
|
||||
|
||||
3. Click *Open Text*
|
||||
4. At the top of the Text Editor, enable Geometry Script's *Auto Resolve* feature. This will make sure the opened file automatically accepts any changes made in an external editor.
|
||||
|
||||
![A screenshot of the top of the Text Editor, with the Auto Resolve option checked](resources/auto_resolve.png)
|
||||
![A screenshot of the top of the Text Editor, with the Auto Resolve option checked](book/src/images/auto_resolve.png)
|
||||
|
||||
5. *(Optional)* Enable *Text* > *Live Edit* to automatically rebuild the Geometry Node tree every time the file is changed.
|
||||
|
||||
![A screenshot of the top of the Text Editor, with the Live Edit option checked](resources/live_edit.png)
|
||||
![A screenshot of the top of the Text Editor, with the Live Edit option checked](book/src/images/live_edit.png)
|
||||
|
||||
### Documentation
|
||||
Documentation and typeshed files are automatically generated when you install the add-on. You can find instructions for using them with your IDE in the add-on preferences.
|
||||
|
||||
![IDE screenshot showing the available documentation for the `grid` function](resources/ide_docs.png)
|
||||
![IDE screenshot showing the available documentation for the `grid` function](book/src/images/ide_docs.png)
|
15
api/types.py
|
@ -57,11 +57,20 @@ class Type:
|
|||
compare_node = State.current_node_tree.nodes.new('FunctionNodeCompare')
|
||||
compare_node.data_type = 'FLOAT' if self._socket.type == 'VALUE' else self._socket.type
|
||||
compare_node.operation = operation
|
||||
State.current_node_tree.links.new(self._socket, compare_node.inputs[0])
|
||||
a = None
|
||||
b = None
|
||||
for node_input in compare_node.inputs:
|
||||
if not node_input.enabled:
|
||||
continue
|
||||
elif a is None:
|
||||
a = node_input
|
||||
else:
|
||||
b = node_input
|
||||
State.current_node_tree.links.new(self._socket, a)
|
||||
if issubclass(type(other), Type):
|
||||
State.current_node_tree.links.new(other._socket, compare_node.inputs[1])
|
||||
State.current_node_tree.links.new(other._socket, b)
|
||||
else:
|
||||
compare_node.inputs[1].default_value = other
|
||||
b.default_value = other
|
||||
return Type(compare_node.outputs[0])
|
||||
|
||||
def __eq__(self, other):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
book
|
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Carson Katri"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Geometry Script"
|
|
@ -0,0 +1,25 @@
|
|||
# Summary
|
||||
|
||||
[Introduction](./introduction.md)
|
||||
|
||||
# Setup
|
||||
|
||||
- [Installation](./setup/installation.md)
|
||||
- [Internal Editing Basics](./setup/internal-editing-basics.md)
|
||||
- [External Editing](./setup/external-editing.md)
|
||||
|
||||
# API
|
||||
|
||||
- [Basics](./api/basics.md)
|
||||
- [Modules](./api/basics/modules.md)
|
||||
- [Tree Functions](./api/basics/tree-functions.md)
|
||||
- [Sockets](./api/basics/sockets.md)
|
||||
- [Using Nodes](./api/basics/using-nodes.md)
|
||||
- [Advanced Scripting](./api/advanced-scripting.md)
|
||||
- [Node Groups](./api/advanced-scripting/node-groups.md)
|
||||
- [Generators](./api/advanced-scripting/generators.md)
|
||||
|
||||
# Tutorials
|
||||
|
||||
- [Voxelize](./tutorials/voxelize.md)
|
||||
- [City Builder](./tutorials/city-builder.md)
|
|
@ -0,0 +1,3 @@
|
|||
# Advanced Scripting
|
||||
|
||||
Now that we've covered the basics, let's take a look at some more advanced scripting techniques.
|
Po Szerokość: | Wysokość: | Rozmiar: 104 KiB |
|
@ -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.
|
Po Szerokość: | Wysokość: | Rozmiar: 148 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 186 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 130 KiB |
|
@ -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.
|
|
@ -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.
|
Po Szerokość: | Wysokość: | Rozmiar: 32 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 34 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 82 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 110 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 134 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 36 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 60 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 114 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 261 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 102 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 45 KiB |
|
@ -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.
|
Po Szerokość: | Wysokość: | Rozmiar: 20 KiB |
|
@ -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))
|
||||
```
|
|
@ -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)
|
|
@ -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(...)
|
||||
```
|
Po Szerokość: | Wysokość: | Rozmiar: 138 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 20 KiB Po Szerokość: | Wysokość: | Rozmiar: 20 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 97 KiB Po Szerokość: | Wysokość: | Rozmiar: 97 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 215 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 90 KiB Po Szerokość: | Wysokość: | Rozmiar: 90 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 48 KiB Po Szerokość: | Wysokość: | Rozmiar: 48 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 103 KiB Po Szerokość: | Wysokość: | Rozmiar: 103 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 13 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 21 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 129 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 288 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 54 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 19 KiB Po Szerokość: | Wysokość: | Rozmiar: 19 KiB |
|
@ -0,0 +1,28 @@
|
|||
# Introduction
|
||||
|
||||
**Geometry Script** is a scripting API for Blender's Geometry Nodes.
|
||||
It makes complicated node trees more managable and easy to share.
|
||||
|
||||
* [Full coverage of nodes](/available_nodes) available in your Blender version
|
||||
* Clean, easy to use [Python API](#introduction)
|
||||
* External [IDE integration](#introduction) for better completions and hot reload
|
||||
|
||||
Here's a simple example of what's possible with a short script:
|
||||
|
||||
### Geometry Script
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Repeat Grid")
|
||||
def repeat_grid(geometry: Geometry, width: Int, height: Int):
|
||||
g = grid(
|
||||
size_x=width, size_y=height,
|
||||
vertices_x=width, vertices_y=height
|
||||
).mesh_to_points()
|
||||
return g.instance_on_points(instance=geometry)
|
||||
```
|
||||
|
||||
### Generated Node Tree
|
||||
|
||||
![Generated node tree](images/example_generated_tree.png)
|
|
@ -0,0 +1,47 @@
|
|||
# External Editing
|
||||
|
||||
Blender's *Text Editor* leaves a lot to be desired. Writing scripts without code completion can be tough.
|
||||
Using an external code editor is one way to improve the editing experience.
|
||||
|
||||
This guide will show how to setup [Visual Studio Code](https://code.visualstudio.com/) to edit Geometry Scripts. However, the same concepts apply to IDEs.
|
||||
|
||||
> This guide assumes you have already installed Visual Studio Code and setup the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python). If not, please follow the setup guides for those tools before continuing.
|
||||
|
||||
## Code Completion
|
||||
When the Geometry Script add-on starts, it generates a Python typeshed file that can be used to provide code completion.
|
||||
All we have to do is add the right path to the Python extension's configuration:
|
||||
|
||||
1. Open Blender preferences and expand the *Geometry Script* preferences
|
||||
2. Copy the *Typeshed Path*
|
||||
|
||||
![A screenshot of the Geometry Script preferences](../images/addon_preferences.png)
|
||||
|
||||
3. In VS Code, open the Settings UI (`Shift+CTRL+P` or `Shift+CMD+P` > `Preferences > Open Settings (UI)`)
|
||||
4. Find the setting `Python > Analysis: Extra Paths`
|
||||
5. Click *Add Item*, then paste in the path copied from Blender and click *OK*
|
||||
|
||||
![A screenshot of the Python > Analysis: Extra Paths setting with the path pasted in](../images/vscode_extra_paths.png)
|
||||
|
||||
6. Create a new Python file, such as `Repeat Grid.py` and start writing a script. As you type, you should get helpful suggestions for every available node.
|
||||
|
||||
![A screenshot of a script with the documentation for `instance_on_points` appearing as the user types.](../images/vscode_code_completion.png)
|
||||
|
||||
## Linking with Blender
|
||||
Writing a script is great, but we want to see it run in Blender. Thankfully, Blender's Text Editor lets us link with an external file, and a simple tool from Geometry Script can make the process more seamless:
|
||||
|
||||
1. Open a *Text Editor* space.
|
||||
2. Click the open button in the top of the editor, and navigate to your Python file.
|
||||
3. Click the gear icon or press *N*, and uncheck *Make Internal*. This will ensure that changes made outside of Blender can be easily brought in.
|
||||
4. Click *Open Text*.
|
||||
|
||||
![A screenshot of Blender's file picker, with the Make Internal checkbox unchecked.](../images/open_file.png)
|
||||
|
||||
5. At the top right of the Text Editor, open the *Geometry Script* menu and enable *Auto Resolve*. Enabling this feature will make the text data-block in Blender update every time you save the file outside of Blender.
|
||||
|
||||
![A screenshot of the Geometry Script menu with Auto Resolve checked](../images/auto_resolve.png)
|
||||
|
||||
6. To enable hot reload, open the *Text* menu and enable *Live Edit*. This will re-run your Geometry Script whenever it changes, updating the node tree live.
|
||||
|
||||
![A screenshot of the Text menu with Live Edit checked](../images/live_edit.png)
|
||||
|
||||
And that's it! You're setup to start writing scripts. In the next section we'll take a look at the API, and all of the things you can do with it.
|
|
@ -0,0 +1,14 @@
|
|||
# Installation
|
||||
|
||||
The add-on is available on GitHub and Blender Market.
|
||||
Choose where you want to get it from and follow the steps below:
|
||||
|
||||
## From GitHub
|
||||
1. [Download the source code](https://github.com/carson-katri/geometry-script/archive/refs/heads/main.zip)
|
||||
2. Open *Blender* > *Preferences* > *Add-ons*
|
||||
3. Choose *Install...* and select the downloaded ZIP file
|
||||
|
||||
## From Blender Market
|
||||
1. After [purchasing the add-on](https://www.blendermarket.com/), download the ZIP file
|
||||
2. Open *Blender* > *Preferences* > *Add-ons*
|
||||
3. Choose *Install...* and select the downloaded ZIP file
|
|
@ -0,0 +1,34 @@
|
|||
# Internal Editing Basics
|
||||
|
||||
The fastest way to get up and running is with Blender's built-in *Text Editor*.
|
||||
You can edit and execute your scripts right inside of Blender:
|
||||
|
||||
1. Open a *Text Editor* space.
|
||||
|
||||
![A screenshot of the available spaces, with the Text Editor space highlighted](../images/text_editor_space.png)
|
||||
|
||||
2. Create a new text data-block with the *New* button.
|
||||
|
||||
![A screenshot of the Text Editor space with the new button](../images/text_editor_new.png)
|
||||
|
||||
3. Start writing a Geometry Script. As an example, you can paste in the script below. More detailed instructions on writing scripts are in later chapters.
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Repeat Grid")
|
||||
def repeat_grid(geometry: Geometry, width: Int, height: Int):
|
||||
g = grid(
|
||||
size_x=width, size_y=height,
|
||||
vertices_x=width, vertices_y=height
|
||||
).mesh_to_points()
|
||||
return g.instance_on_points(instance=geometry)
|
||||
```
|
||||
|
||||
4. Click the run button to execute the script. This will create a Geometry Nodes tree named *Repeat Grid*.
|
||||
|
||||
![A screenshot of the Text Editor space with the Run Script button](../images/text_editor_run_script.png)
|
||||
|
||||
5. Create a *Geometry Nodes* modifier on any object in your scene and select the *Repeat Grid* tree.
|
||||
|
||||
![A screenshot of the Blender window with a 3x3 grid of cubes on the left and a Geometry Nodes modifier with the Repeat Grid tree selected on the right](../images/geometry_nodes_modifier.png)
|
|
@ -0,0 +1,119 @@
|
|||
# City Builder
|
||||
|
||||
In this tutorial we'll create a dense grid of buildings, then cut away from them to place roads with curves. We'll also make use of a [generator](../api/advanced-scripting/generators.md) to combine the buildings with the roads.
|
||||
|
||||
![](./city_builder.gif)
|
||||
|
||||
## Setting Up
|
||||
Create a Bezier Curve object. You can enter edit mode and delete the default curve it creates.
|
||||
|
||||
Then create a new script. Setting up an [external editor](../setup/external-editing.md) is recommended.
|
||||
|
||||
Import Geometry Script, and create a basic tree builder function. We'll add a few arguments to configure the buildings.
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("City Builder")
|
||||
def city_builder(
|
||||
geometry: Geometry,
|
||||
seed: Int,
|
||||
road_width: Float = 0.25,
|
||||
size_x: Float = 5, size_y: Float = 5, density: Float = 10,
|
||||
building_size_min: Vector = (0.1, 0.1, 0.2),
|
||||
building_size_max: Vector = (0.3, 0.3, 1),
|
||||
):
|
||||
return geometry
|
||||
```
|
||||
|
||||
Run the script to create the tree, then add a *Geometry Nodes* modifier to your curve object and select the *City Builger* node group.
|
||||
|
||||
## Buildings
|
||||
Let's start with the buildings. We'll distribute points on a grid with `size_x` and `size_y`.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
building_points = grid(size_x=size_x, size_y=size_y).distribute_points_on_faces(density=density, seed=seed).points
|
||||
return building_points
|
||||
```
|
||||
|
||||
Next, we'll instance cubes on these points to serve as our buildings. We move the cube object up half its height so the buildings sit flat on the grid, and scale them randomly between the min and max sizes.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
...
|
||||
return building_points.instance_on_points(
|
||||
instance=cube().transform(translation=(0, 0, 0.5)),
|
||||
scale=random_value(data_type='FLOAT_VECTOR', min=building_size_min, max=building_size_max, seed=seed),
|
||||
)
|
||||
```
|
||||
|
||||
## Roads
|
||||
Using `curve_to_mesh`, we can turn the input curve into a flat mesh. We'll use the `yield` keyword to join the curve mesh and the building mesh automatically. Change the `building_points.instance_on_points` line to use `yield` for this to work.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
yield geometry.curve_to_mesh(profile_curve=curve_line(
|
||||
start=combine_xyz(x=road_width * -0.5),
|
||||
end=combine_xyz(x=road_width * 0.5)
|
||||
))
|
||||
...
|
||||
yield building_points.instance_on_points(...)
|
||||
```
|
||||
|
||||
But now the buildings are overlapping the road. We need to remove any point that falls within the road curve. We'll use `geometry_proximity` and `delete_geometry` to find and remove these invalid points.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
...
|
||||
building_points = ...
|
||||
road_points = geometry.curve_to_points(mode='EVALUATED').points
|
||||
building_points = building_points.delete_geometry(
|
||||
domain='POINT',
|
||||
selection=geometry_proximity(target_element='POINTS', target=road_points, source_position=position()).distance < road_width
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
## Drawing Roads
|
||||
Enter edit mode and select the *Draw* tool. Simply draw roads onto your city to see the buildings and meshes update.
|
||||
|
||||
![](./city_builder.gif)
|
||||
|
||||
## Final Script
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("City Builder")
|
||||
def city_builder(
|
||||
geometry: Geometry,
|
||||
seed: Int,
|
||||
road_width: Float = 0.25,
|
||||
size_x: Float = 5, size_y: Float = 5, density: Float = 10,
|
||||
building_size_min: Vector = (0.1, 0.1, 0.2),
|
||||
building_size_max: Vector = (0.3, 0.3, 1),
|
||||
):
|
||||
# Road mesh
|
||||
yield geometry.curve_to_mesh(profile_curve=curve_line(
|
||||
start=combine_xyz(x=road_width * -0.5),
|
||||
end=combine_xyz(x=road_width * 0.5)
|
||||
))
|
||||
# Building points
|
||||
building_points = grid(size_x=size_x, size_y=size_y).distribute_points_on_faces(density=density, seed=seed).points
|
||||
road_points = geometry.curve_to_points(mode='EVALUATED').points
|
||||
# Delete points within the curve
|
||||
building_points = building_points.delete_geometry(
|
||||
domain='POINT',
|
||||
selection=geometry_proximity(target_element='POINTS', target=road_points, source_position=position()).distance < road_width
|
||||
)
|
||||
# Building instances
|
||||
yield building_points.instance_on_points(
|
||||
instance=cube().transform(translation=(0, 0, 0.5)),
|
||||
scale=random_value(data_type='FLOAT_VECTOR', min=building_size_min, max=building_size_max, seed=seed),
|
||||
)
|
||||
```
|
||||
|
||||
## Generated Node Tree
|
||||
|
||||
![](./city_builder_nodes.png)
|
Po Szerokość: | Wysokość: | Rozmiar: 15 MiB |
Po Szerokość: | Wysokość: | Rozmiar: 419 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 292 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 577 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 392 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 317 KiB |
|
@ -0,0 +1,109 @@
|
|||
# Voxelize
|
||||
|
||||
This tutorial walks you through creating a script that turns any mesh into voxels.
|
||||
|
||||
> This tutorial requires Blender 3.4+ for the *Distribute Points In Volume* node.
|
||||
|
||||
## Setting Up
|
||||
Create a base mesh. I'll be using a Monkey primitive.
|
||||
|
||||
![](./monkey.png)
|
||||
|
||||
Next, create a new script. Setting up an [external editor](../setup/external-editing.md) is recommended.
|
||||
|
||||
Import Geometry Script, and create a basic tree builder function. We'll add a `geometry` argument and annotate it with the `Geometry` type to receive our base mesh (in this case, a monkey).
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Voxelize")
|
||||
def voxelize(geometry: Geometry):
|
||||
return geometry
|
||||
```
|
||||
|
||||
Run the script to create the tree, then add a *Geometry Nodes* modifier to your mesh and select the *Voxelize* node group.
|
||||
|
||||
![](./voxelize_modifier.png)
|
||||
|
||||
## Arguments
|
||||
Add a new argument `resolution: Float`. Give it a default value of `0.2`. This value will be used throughout the script to configure spacing and voxel density.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
...
|
||||
```
|
||||
|
||||
## Mesh to Volume
|
||||
We want to convert the mesh to a hollow volume, so only the outside of the mesh has voxel instances. This will improve the performance of our script.
|
||||
|
||||
Use the `mesh_to_volume` function on the base mesh to convert it to a volume.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume( # Hollow mesh volume
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
)
|
||||
```
|
||||
|
||||
![](./monkey_volume.png)
|
||||
|
||||
## Volume to Points
|
||||
Next, we need to create points to instance each voxel cube on. Use `distribute_points_in_volume` with the mode set to `DENSITY_GRID` to create a uniform distribution of points.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume( # Uniform grid distribution
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
)
|
||||
```
|
||||
|
||||
![](./monkey_points.png)
|
||||
|
||||
## Instance Cubes
|
||||
Finally, use `instance_on_points` with a cube of size `resolution` to instance a cube on each point created from our mesh.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume(
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
).instance_on_points( # Cube instancing
|
||||
instance=cube(size=resolution)
|
||||
)
|
||||
```
|
||||
|
||||
![](./monkey_voxels.png)
|
||||
|
||||
You can lower the resolution to get smaller, more detailed voxels, or raise it to get larger voxels.
|
||||
|
||||
## Final Script
|
||||
|
||||
```python
|
||||
# NOTE: This example requires Blender 3.4+
|
||||
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Voxelize")
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume(
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
).instance_on_points(
|
||||
instance=cube(size=resolution)
|
||||
)
|
||||
```
|
||||
|
||||
## Generated Node Tree
|
||||
|
||||
![](./voxelize_nodes.png)
|
Po Szerokość: | Wysokość: | Rozmiar: 32 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 170 KiB |