pull/10/head
Carson Katri 2022-11-15 12:59:28 -05:00
rodzic 3850ec011c
commit 146e83f8ae
56 zmienionych plików z 817 dodań i 9 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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):

1
book/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1 @@
book

6
book/book.toml 100644
Wyświetl plik

@ -0,0 +1,6 @@
[book]
authors = ["Carson Katri"]
language = "en"
multilingual = false
src = "src"
title = "Geometry Script"

Wyświetl plik

@ -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)

Wyświetl plik

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

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 104 KiB

Wyświetl plik

@ -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.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 148 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 186 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 130 KiB

Wyświetl plik

@ -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.

Wyświetl plik

@ -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.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 32 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 34 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 82 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 110 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 134 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 36 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 60 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 114 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 261 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 102 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 45 KiB

Wyświetl plik

@ -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.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 20 KiB

Wyświetl plik

@ -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))
```

Wyświetl plik

@ -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)

Wyświetl plik

@ -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(...)
```

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 138 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 20 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 20 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 97 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 97 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 215 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 48 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 48 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 103 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 103 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 13 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 21 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 129 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 288 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 54 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 19 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 19 KiB

Wyświetl plik

@ -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)

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 15 MiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 419 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 292 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 577 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 392 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 317 KiB

Wyświetl plik

@ -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)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 32 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 170 KiB