blender-geometry-script/print.html

1053 wiersze
62 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js coal">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Geometry Script</title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "coal" : "coal";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('coal')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="introduction.html">Introduction</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html"><strong aria-hidden="true">1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="setup/internal-editing-basics.html"><strong aria-hidden="true">2.</strong> Internal Editing Basics</a></li><li class="chapter-item expanded "><a href="setup/external-editing.html"><strong aria-hidden="true">3.</strong> External Editing</a></li><li class="chapter-item expanded affix "><li class="part-title">API</li><li class="chapter-item expanded "><a href="api/basics.html"><strong aria-hidden="true">4.</strong> Basics</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="api/basics/modules.html"><strong aria-hidden="true">4.1.</strong> Modules</a></li><li class="chapter-item expanded "><a href="api/basics/tree-functions.html"><strong aria-hidden="true">4.2.</strong> Tree Functions</a></li><li class="chapter-item expanded "><a href="api/basics/sockets.html"><strong aria-hidden="true">4.3.</strong> Sockets</a></li><li class="chapter-item expanded "><a href="api/basics/using-nodes.html"><strong aria-hidden="true">4.4.</strong> Using Nodes</a></li></ol></li><li class="chapter-item expanded "><a href="api/advanced-scripting.html"><strong aria-hidden="true">5.</strong> Advanced Scripting</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="api/advanced-scripting/node-groups.html"><strong aria-hidden="true">5.1.</strong> Node Groups</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/generators.html"><strong aria-hidden="true">5.2.</strong> Generators</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/input-groups.html"><strong aria-hidden="true">5.3.</strong> Input Groups</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/attributes.html"><strong aria-hidden="true">5.4.</strong> Attributes</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/boolean-math.html"><strong aria-hidden="true">5.5.</strong> Boolean Math</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/drivers.html"><strong aria-hidden="true">5.6.</strong> Drivers</a></li><li class="chapter-item expanded "><a href="api/advanced-scripting/simulation.html"><strong aria-hidden="true">5.7.</strong> Simulation</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">Tutorials</li><li class="chapter-item expanded "><a href="tutorials/voxelize.html"><strong aria-hidden="true">6.</strong> Voxelize</a></li><li class="chapter-item expanded "><a href="tutorials/city-builder.html"><strong aria-hidden="true">7.</strong> City Builder</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Geometry Script</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p><strong>Geometry Script</strong> is a scripting API for Blender's Geometry Nodes.
It makes complicated node trees more managable and easy to share.</p>
<ul>
<li><a href="./api/basics/using-nodes.html">Full coverage of nodes</a> available in your Blender version</li>
<li>Clean, easy to use <a href="./api/basics.html">Python API</a></li>
<li>External <a href="./setup/external-editing.html">IDE integration</a> for better completions and hot reload</li>
</ul>
<p>Here's a simple example of what's possible with a short script:</p>
<h3 id="geometry-script"><a class="header" href="#geometry-script">Geometry Script</a></h3>
<pre><code class="language-python">from geometry_script import *
@tree(&quot;Repeat Grid&quot;)
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)
</code></pre>
<h3 id="generated-node-tree"><a class="header" href="#generated-node-tree">Generated Node Tree</a></h3>
<p><img src="images/example_generated_tree.png" alt="Generated node tree" /></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation"><a class="header" href="#installation">Installation</a></h1>
<p>The add-on is available on GitHub and Blender Market.
Choose where you want to get it from and follow the steps below:</p>
<h2 id="from-github"><a class="header" href="#from-github">From GitHub</a></h2>
<ol>
<li><a href="https://github.com/carson-katri/geometry-script/archive/refs/heads/main.zip">Download the source code</a></li>
<li>Open <em>Blender</em> &gt; <em>Preferences</em> &gt; <em>Add-ons</em></li>
<li>Choose <em>Install...</em> and select the downloaded ZIP file</li>
</ol>
<h2 id="from-blender-market"><a class="header" href="#from-blender-market">From Blender Market</a></h2>
<ol>
<li>After <a href="https://www.blendermarket.com/">purchasing the add-on</a>, download the ZIP file</li>
<li>Open <em>Blender</em> &gt; <em>Preferences</em> &gt; <em>Add-ons</em></li>
<li>Choose <em>Install...</em> and select the downloaded ZIP file</li>
</ol>
<div style="break-before: page; page-break-before: always;"></div><h1 id="internal-editing-basics"><a class="header" href="#internal-editing-basics">Internal Editing Basics</a></h1>
<p>The fastest way to get up and running is with Blender's built-in <em>Text Editor</em>.
You can edit and execute your scripts right inside of Blender:</p>
<ol>
<li>Open a <em>Text Editor</em> space.</li>
</ol>
<p><img src="setup/../images/text_editor_space.png" alt="A screenshot of the available spaces, with the Text Editor space highlighted" /></p>
<ol start="2">
<li>Create a new text data-block with the <em>New</em> button.</li>
</ol>
<p><img src="setup/../images/text_editor_new.png" alt="A screenshot of the Text Editor space with the new button" /></p>
<ol start="3">
<li>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.</li>
</ol>
<pre><code class="language-python">from geometry_script import *
@tree(&quot;Repeat Grid&quot;)
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)
</code></pre>
<ol start="4">
<li>Click the run button to execute the script. This will create a Geometry Nodes tree named <em>Repeat Grid</em>.</li>
</ol>
<p><img src="setup/../images/text_editor_run_script.png" alt="A screenshot of the Text Editor space with the Run Script button" /></p>
<ol start="5">
<li>Create a <em>Geometry Nodes</em> modifier on any object in your scene and select the <em>Repeat Grid</em> tree.</li>
</ol>
<p><img src="setup/../images/geometry_nodes_modifier.png" alt="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" /></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="external-editing"><a class="header" href="#external-editing">External Editing</a></h1>
<p>Blender's <em>Text Editor</em> 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.</p>
<p>This guide will show how to setup <a href="https://code.visualstudio.com/">Visual Studio Code</a> to edit Geometry Scripts. However, the same concepts apply to other IDEs.</p>
<blockquote>
<p>This guide assumes you have already installed Visual Studio Code and setup the <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python">Python extension</a>. If not, please setup those tools before continuing.</p>
</blockquote>
<h2 id="code-completion"><a class="header" href="#code-completion">Code Completion</a></h2>
<p>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:</p>
<ol>
<li>Open Blender preferences and expand the <em>Geometry Script</em> preferences</li>
<li>Copy the <em>Typeshed Path</em></li>
</ol>
<p><img src="setup/../images/addon_preferences.png" alt="A screenshot of the Geometry Script preferences" /></p>
<ol start="3">
<li>In VS Code, open the Settings UI (<code>Shift+CTRL+P</code> or <code>Shift+CMD+P</code> &gt; <code>Preferences &gt; Open Settings (UI)</code>)</li>
<li>Find the setting <code>Python &gt; Analysis: Extra Paths</code></li>
<li>Click <em>Add Item</em>, then paste in the path copied from Blender and click <em>OK</em></li>
</ol>
<p><img src="setup/../images/vscode_extra_paths.png" alt="A screenshot of the Python &gt; Analysis: Extra Paths setting with the path pasted in" /></p>
<ol start="6">
<li>Create a new Python file, such as <code>Repeat Grid.py</code> and start writing a script. As you type, you should get helpful suggestions for every available node.</li>
</ol>
<p><img src="setup/../images/vscode_code_completion.png" alt="A screenshot of a script with the documentation for instance_on_points appearing as the user types." /></p>
<h2 id="linking-with-blender"><a class="header" href="#linking-with-blender">Linking with Blender</a></h2>
<p>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:</p>
<ol>
<li>Open a <em>Text Editor</em> space.</li>
<li>Click the open button in the top of the editor, and navigate to your Python file.</li>
<li>Click the gear icon or press <em>N</em>, and uncheck <em>Make Internal</em>. This will ensure that changes made outside of Blender can be easily brought in.</li>
<li>Click <em>Open Text</em>.</li>
</ol>
<p><img src="setup/../images/open_file.png" alt="A screenshot of Blender's file picker, with the Make Internal checkbox unchecked." /></p>
<ol start="5">
<li>At the top right of the Text Editor, open the <em>Geometry Script</em> menu and enable <em>Auto Resolve</em>. Enabling this feature will make the text data-block in Blender update every time you save the file outside of Blender.</li>
</ol>
<p><img src="setup/../images/auto_resolve.png" alt="A screenshot of the Geometry Script menu with Auto Resolve checked" /></p>
<ol start="6">
<li>To enable hot reload, open the <em>Text</em> menu and enable <em>Live Edit</em>. This will re-run your Geometry Script whenever it changes, updating the node tree live.</li>
</ol>
<p><img src="setup/../images/live_edit.png" alt="A screenshot of the Text menu with Live Edit checked" /></p>
<p>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.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="basics"><a class="header" href="#basics">Basics</a></h1>
<p>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.</p>
<p>Setting up an editor for <a href="api/../setup/external-editing.html">external editing</a> is recommended when writing scripts, but <a href="api/../setup/internal-editing-basics.html">internal editing inside Blender</a> will suffice for the simple examples shown here.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="modules"><a class="header" href="#modules">Modules</a></h1>
<p>The first step when writing is script is importing the <code>geometry_script</code> module. There a are a few ways of doing this:</p>
<h2 id="import-all-names-recommended"><a class="header" href="#import-all-names-recommended">Import All Names (Recommended)</a></h2>
<p>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.</p>
<pre><code class="language-python">from geometry_script import *
cube(...) # Available globally
my_geo: Geometry # All types available as well
</code></pre>
<h2 id="import-specific-names"><a class="header" href="#import-specific-names">Import Specific Names</a></h2>
<p>This will import only the specified names from the module:</p>
<pre><code class="language-python">from geometry_script import cube, Geometry
cube(...) # Available from import
my_geo: Geometry
</code></pre>
<h2 id="namespaced-import"><a class="header" href="#namespaced-import">Namespaced Import</a></h2>
<p>This will import every type and function, and place them behind the namespace. You can use the module name, or provide your own.</p>
<pre><code class="language-python">import geometry_script
geometry_script.cube(...) # Prefix with the namespace
my_geo: geometry_script.Geometry
</code></pre>
<pre><code class="language-python">import geometry_script as gs
gs.cube(...) # Prefix with the custom name
my_geo: gs.Geometry
</code></pre>
<p>Now that you have Geometry Script imported in some way, let's create a tree.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="tree-functions"><a class="header" href="#tree-functions">Tree Functions</a></h1>
<p>Node trees are created by decorating a function with <code>@tree</code>. Let's try creating a simple tree function.</p>
<blockquote>
<p>The code samples for the rest of the book assume you are importing all names with <code>from geometry_script import *</code>. However, if you are using a namespaced import, simply prefix the functions and types with <code>geometry_script</code> or your custom name.</p>
</blockquote>
<pre><code class="language-python">@tree
def cube_tree():
...
</code></pre>
<p>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 <code>@tree</code>:</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree():
...
</code></pre>
<h2 id="group-output"><a class="header" href="#group-output">Group Output</a></h2>
<p>Every node tree is <strong>required</strong> to return <code>Geometry</code> as the first output. Let's try returning a simple cube.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree():
return cube()
</code></pre>
<p>Here we call the <code>cube(...)</code> function, which creates a <em>Cube</em> node and connects it to the <em>Group Output</em>.</p>
<p><img src="api/basics/./cube_tree.png" alt="" /></p>
<p>You can also return multiple values. However, <code>Geometry</code> must always be returned first for a tree to be valid.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree():
return cube(), 5
</code></pre>
<p><img src="api/basics/./cube_tree_int.png" alt="" /></p>
<p>By default, each output is named 'Result'. To customize the name, return a dictionary.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree():
return {
&quot;My Cube&quot;: cube(),
&quot;Scale Constant&quot;: 5
}
</code></pre>
<p><img src="api/basics/./cube_tree_named_outputs.png" alt="" /></p>
<h2 id="group-input"><a class="header" href="#group-input">Group Input</a></h2>
<p>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.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
return cube(size=size)
</code></pre>
<p>This creates a <em>Size</em> socket on the <em>Group Input</em> node and connects it to our cube.</p>
<p><img src="api/basics/./cube_tree_size.png" alt="" /></p>
<p>The option is available on the Geometry Nodes modifier.</p>
<p><img src="api/basics/./cube_tree_modifier.png" alt="" /></p>
<p>The available socket types match those in the UI. Here are some common ones:</p>
<ul>
<li><code>Geometry</code></li>
<li><code>Float</code></li>
<li><code>Int</code></li>
<li><code>Vector</code></li>
</ul>
<blockquote>
<p>You <em>cannot</em> use Python's built-in types in place of these socket types.</p>
</blockquote>
<p>In the next chapter, we'll take a closer look at how socket types work, and what you can and cannot do with them.</p>
<h3 id="default-values"><a class="header" href="#default-values">Default Values</a></h3>
<p>You can specify a default for any argument, and it will be set on the modifier when added:</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector = (1, 1, 1)):
return cube(size=size)
</code></pre>
<p><img src="api/basics/./cube_tree_size_input.png" alt="" /></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="sockets"><a class="header" href="#sockets">Sockets</a></h1>
<p>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.</p>
<h2 id="what-is-a-socket"><a class="header" href="#what-is-a-socket">What is a socket?</a></h2>
<p>A socket is any input or output on a node. Take the <em>Cube</em> node for example:</p>
<p><img src="api/basics/./cube_node.png" alt="" /></p>
<p>This node has 4 input sockets, and 1 output socket.</p>
<ul>
<li>Input Sockets
<ul>
<li>Size: <code>Vector</code></li>
<li>Vertices X: <code>Int</code></li>
<li>Vertices Y: <code>Int</code></li>
<li>Vertices Z: <code>Int</code></li>
</ul>
</li>
<li>Output Sockets
<ul>
<li>Mesh: <code>Geometry</code></li>
</ul>
</li>
</ul>
<p>A socket does not represent a value itself. For example, the <code>Size</code> socket does not necessarily represent the value <code>(1, 1, 1)</code>. Instead, it can be connected to another node as an input, giving it a dynamic value.</p>
<p>When we write scripts, we typically deal with socket types, not concrete values like <code>(1, 1, 1)</code>. Take this script for example:</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
return cube(size=size)
</code></pre>
<p>The <code>size</code> argument creates a input socket with the type <code>Vector</code>. This is then connected to the <code>size</code> socket of the <em>Cube</em> node.</p>
<p><img src="api/basics/./cube_tree_size.png" alt="" /></p>
<p>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 <code>size</code> has when the script runs, because it is dynamic.</p>
<h2 id="what-sockets-can-do"><a class="header" href="#what-sockets-can-do">What sockets <em>can</em> do</a></h2>
<p>Sockets are great for passing values between nodes. A socket type like <code>Geometry</code> 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.</p>
<h2 id="what-sockets-cannot-do"><a class="header" href="#what-sockets-cannot-do">What sockets <em>cannot</em> do</a></h2>
<p>Sockets cannot be read for their concrete value. A <code>Float</code> socket type does not equal <code>5</code> or <code>10</code> or <code>3.14</code> to our script. It only represents the socket of a node. If you try to <code>print(...)</code> a socket, you will receive a generic reference type with no underlying value.</p>
<h2 id="why-use-sockets"><a class="header" href="#why-use-sockets">Why use sockets?</a></h2>
<p>You might be wondering, &quot;if you can't access the value of a socket, what can you do with it?&quot;</p>
<p>Geometry Script provides many helpful additions that make working with sockets about as easy as working with a concrete value.</p>
<h2 id="socket-math"><a class="header" href="#socket-math">Socket Math</a></h2>
<p>Socket types can be used to perform math operations. The proper <em>Math</em> node will be created automatically for you, so you can focus on writing a script and not thinking about sockets. If you use <code>Float</code> or <code>Int</code> it will create a <em>Math</em> node, and if you use a <code>Vector</code> it will create a <em>Vector Math</em> node.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
doubled = size * (2, 2, 2) # Multiply each component by 2
return cube(size=doubled)
</code></pre>
<p><img src="api/basics/./cube_tree_size_double.png" alt="" /></p>
<p>Several common math operations are available, such as:</p>
<ul>
<li>Add (<code>socket + 2</code>)</li>
<li>Subtract (<code>socket - 2</code>)</li>
<li>Multiply (<code>socket * 2</code>)</li>
<li>Divide (<code>socket / 2</code>)</li>
<li>Modulo (<code>socket % 2</code>)</li>
</ul>
<h2 id="socket-comparison"><a class="header" href="#socket-comparison">Socket Comparison</a></h2>
<p>Socket types can be compared with Python comparison operators. A <em>Compare</em> node will be created with the correct inputs and options specified.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
show_cube = size &gt; (2, 2, 2) # Check if each component is greater than 2
return cube(size=show_cube)
</code></pre>
<p><img src="api/basics/./cube_tree_size_compare.png" alt="" /></p>
<p>Several common comparison operators are supported, such as:</p>
<ul>
<li>Equal To (<code>socket == 2</code>)</li>
<li>Not Equal To (<code>socket != 2</code>)</li>
<li>Less Than (<code>socket &lt; 2</code>)</li>
<li>Less Than Or Equal To (<code>socket &lt;= 2</code>)</li>
<li>Greater Than (<code>socket &gt; 2</code>)</li>
<li>Greater Than Or Equal To (<code>socket &gt;= 2</code>)</li>
</ul>
<h2 id="vector-component-properties"><a class="header" href="#vector-component-properties">Vector Component Properties</a></h2>
<p>While the <code>Vector</code> type does not equate to three concrete components, such as <code>(1, 2, 3)</code>, you can still access the <code>x</code>, <code>y</code>, and <code>z</code> components as sockets. A <em>Separate XYZ</em> node will be created with the correct inputs and outputs specified.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
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))
</code></pre>
<p>For each component access, a <em>Separate XYZ</em> node is created.</p>
<p><img src="api/basics/./cube_tree_size_components.png" alt="" /></p>
<h2 id="chained-calls"><a class="header" href="#chained-calls">Chained Calls</a></h2>
<p>Any node function can be called on a socket type. This will automatically connect the socket to the first input of the node.</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
return cube(size=size).mesh_to_volume()
</code></pre>
<p>The output of the <em>Cube</em> node (a <code>Geometry</code> socket type) is connected to the first input of the <em>Mesh to Volume</em> node.</p>
<p><img src="api/basics/./cube_tree_mesh_to_volume.png" alt="" /></p>
<p>The same script without chaining calls is written more verbosely as:</p>
<pre><code class="language-python">@tree(&quot;Cube Tree&quot;)
def cube_tree(size: Vector):
return mesh_to_volume(mesh=cube(size=size))
</code></pre>
<h3 id="spanning-multiple-lines"><a class="header" href="#spanning-multiple-lines">Spanning Multiple Lines</a></h3>
<p>Often times you want each chained calls to be on a separate line. There are a few ways to do this in Python:</p>
<ol>
<li>Newlines around arguments</li>
</ol>
<pre><code class="language-python">cube(
size=size
).mesh_to_volume()
</code></pre>
<ol start="2">
<li>Parentheses</li>
</ol>
<pre><code class="language-python">(cube(size=size)
.mesh_to_volume())
</code></pre>
<ol start="3">
<li>Line continuation</li>
</ol>
<pre><code class="language-python">cube(size=size) \
.mesh_to_volume()
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="using-nodes"><a class="header" href="#using-nodes">Using Nodes</a></h1>
<p>Node functions are automatically generated for the Blender version you are using. This means every node will be available from geometry script.</p>
<p>This means that when future versions of Blender add new nodes, they will all be available in Geometry Script without updating the add-on.</p>
<p>To see all of the node functions available in your Blender version, open the <em>Geometry Script</em> menu in the <em>Text Editor</em> and click <em>Open Documentation</em>.</p>
<p><img src="api/basics/./open_documentation.png" alt="" /></p>
<p>This will open the automatically generated docs page with a list of every available node and it's inputs and outputs.</p>
<h2 id="how-nodes-are-mapped"><a class="header" href="#how-nodes-are-mapped">How nodes are mapped</a></h2>
<p>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 <a href="api/basics/../../setup/external-editing.html">IDE with code completion</a> makes this even easier.</p>
<p>The general process is:</p>
<ol>
<li>Convert the node name to snake case.</li>
<li>Add a keyword argument (in snake case) for each property and input.</li>
<li>If the node has a single output, return the socket type, otherwise return an object with properties for each output name.</li>
</ol>
<blockquote>
<p>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.</p>
</blockquote>
<h2 id="enum-properties"><a class="header" href="#enum-properties">Enum Properties</a></h2>
<p>Many nodes have enum properties. For example, the math node lets you choose which operation to perform. You can pass a string to specify the enum case to use. But a safer way to set these values is with the autogenerated enum types. The enums are namespaced to the name of the node in PascalCase:</p>
<pre><code class="language-python"># Access it by Node.Enum Name.Case
math(operation=Math.Operation.ADD)
math(operation=Math.Operation.SUBTRACT)
math(operation='MULTIPLY') # Or manually pass a string
</code></pre>
<p>Internally, this type is generated as:</p>
<pre><code class="language-python">import enum
class Math:
class Operation(enum.Enum):
ADD = 'ADD'
SUBTRACT = 'SUBTRACT'
MULTIPLY = 'MULTIPLY'
...
...
</code></pre>
<p>The cases will appear in code completion if you setup an <a href="api/basics/../../setup/external-editing.html">external editor</a>.</p>
<h2 id="duplicate-names"><a class="header" href="#duplicate-names">Duplicate Names</a></h2>
<p>Some nodes use the same input name multiple times. For example, the <em>Math</em> node has three inputs named <code>value</code>. To specify each value, pass a tuple for the input:</p>
<pre><code class="language-python">math(operation=Math.Operation.WRAP, value=(0.5, 1, 0)) # Pass all 3
math(operation=Math.Operation.WRAP, value=(0.5, 1)) # Only pass 2/3
math(operation=Math.Operation.WRAP, value=0.5) # Only pass 1/3
</code></pre>
<p><img src="api/basics/./math_wrap.png" alt="" /></p>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<p>Here are two examples to show how a node maps to a function.</p>
<h3 id="cube"><a class="header" href="#cube">Cube</a></h3>
<p><img src="api/basics/./cube_node.png" alt="" /></p>
<ol>
<li>Name: <code>Cube</code> -&gt; <code>cube</code></li>
<li>Keyword Arguments
<ul>
<li><code>size: Vector</code></li>
<li><code>vertices_x: Int</code></li>
<li><code>vertices_y: Int</code></li>
<li><code>vertices_z: Int</code></li>
</ul>
</li>
<li>Return <code>Geometry</code></li>
</ol>
<p>The node can now be used as a function:</p>
<pre><code class="language-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
</code></pre>
<p>The generated documentation will show the signature, result type, and <a href="api/basics/./sockets.html#chained-calls">chain syntax</a>.</p>
<h4 id="signature"><a class="header" href="#signature">Signature</a></h4>
<pre><code class="language-python">cube(
size: VectorTranslation,
vertices_x: Int,
vertices_y: Int,
vertices_z: Int
)
</code></pre>
<h4 id="result"><a class="header" href="#result">Result</a></h4>
<pre><code class="language-python">mesh: Geometry
</code></pre>
<h4 id="chain-syntax"><a class="header" href="#chain-syntax">Chain Syntax</a></h4>
<pre><code class="language-python">size: VectorTranslation = ...
size.cube(...)
</code></pre>
<h3 id="capture-attribute"><a class="header" href="#capture-attribute">Capture Attribute</a></h3>
<p><img src="api/basics/./capture_attribute_node.png" alt="" /></p>
<ol>
<li>Name <code>Capture Attribute</code> -&gt; <code>capture_attribute</code></li>
<li>Keyword Arguments
<ul>
<li>Properties
<ul>
<li><code>data_type: CaptureAttribute.DataType</code></li>
<li><code>domain: CaptureAttribute.Domain</code></li>
</ul>
</li>
<li>Inputs
<ul>
<li><code>geometry: Geometry</code></li>
<li><code>value: Vector | Float | Color | Bool | Int</code></li>
</ul>
</li>
</ul>
</li>
<li>Return <code>{ geometry: Geometry, attribute: Int }</code></li>
</ol>
<p>The node can now be used as a function:</p>
<pre><code class="language-python">result = capture_attribute(data_type=CaptureAttribute.DataType.BOOLEAN, geometry=cube_geo) # Specify a property and an input
result.geometry # Access the geometry
result.attribute # Access the attribute
</code></pre>
<p>The generated documentation will show the signature, result type, and <a href="api/basics/./sockets.html#chained-calls">chain syntax</a>.</p>
<h4 id="signature-1"><a class="header" href="#signature-1">Signature</a></h4>
<pre><code class="language-python">capture_attribute(
data_type: CaptureAttribute.DataType,
domain: CaptureAttribute.Domain,
geometry: Geometry,
value: Vector | Float | Color | Bool | Int
)
</code></pre>
<h4 id="result-1"><a class="header" href="#result-1">Result</a></h4>
<pre><code class="language-python">{ geometry: Geometry, attribute: Int }
</code></pre>
<h4 id="chain-syntax-1"><a class="header" href="#chain-syntax-1">Chain Syntax</a></h4>
<pre><code class="language-python">geometry: Geometry = ...
geometry.capture_attribute(...)
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="advanced-scripting"><a class="header" href="#advanced-scripting">Advanced Scripting</a></h1>
<p>Now that we've covered the basics, let's take a look at some more advanced scripting techniques.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="node-groups"><a class="header" href="#node-groups">Node Groups</a></h1>
<p>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 <em>Node Group</em> nodes.</p>
<pre><code class="language-python">@tree(&quot;Instance Grid&quot;)
def instance_grid(instance: Geometry):
&quot;&quot;&quot; Instance the input geometry on a grid &quot;&quot;&quot;
return grid().mesh_to_points().instance_on_points(instance=instance)
@tree(&quot;Cube Grid&quot;)
def cube_grid():
&quot;&quot;&quot; Create a grid of cubes &quot;&quot;&quot;
return instance_grid(instance=cube(size=0.2))
</code></pre>
<p>The <em>Cube Grid</em> tree uses the <em>Instance Grid</em> node group by calling the <code>instance_grid</code> function:</p>
<p><img src="api/advanced-scripting/./cube_grid.png" alt="" /></p>
<p>The <em>Instance Grid</em> node group uses the passed in <code>instance</code> argument to create a grid of instances:</p>
<p><img src="api/advanced-scripting/./instance_grid.png" alt="" /></p>
<p>This concept can scale to complex interconnected node trees, while keeping everything neatly organized in separate functions.</p>
<h2 id="functions-vs-node-groups"><a class="header" href="#functions-vs-node-groups">Functions vs Node Groups</a></h2>
<p>You do not have to mark a function with <code>@tree(...)</code>. If you don't, function calls are treated as normal in Python. No checks are made against the arguments. Any nodes created in the callee will be placed in the caller's tree.</p>
<pre><code class="language-python">def instance_grid(instance: Geometry): # Not marked with `@tree(...)`
return grid().mesh_to_points().instance_on_points(instance=instance)
@tree(&quot;Cube Grid&quot;)
def cube_grid(): # Marked with `@tree(...)`
return instance_grid(instance=cube(size=0.2))
</code></pre>
<p>The above example would place the <em>Grid</em>, <em>Mesh to Points</em>, and <em>Instance on Points</em> nodes in the main &quot;Cube Grid&quot; tree. It could be rewritten as:</p>
<pre><code class="language-python">@tree(&quot;Cube Grid&quot;)
def cube_grid():
return grid().mesh_to_points().instance_on_points(instance=cube(size=0.2))
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="generators"><a class="header" href="#generators">Generators</a></h1>
<p>Python has support for <a href="https://wiki.python.org/moin/Generators">generators</a> using the <code>yield</code> keyword.</p>
<p>Geometry Script tree functions can be represented as generators to output multiple values. If every generated value is <code>Geometry</code>, the values are automatically connected to a <em>Join Geometry</em> node and output as a single mesh.</p>
<pre><code class="language-python">@tree(&quot;Primitive Shapes&quot;)
def primitive_shapes():
yield cube()
yield uv_sphere()
yield cylinder().mesh
</code></pre>
<p><img src="api/advanced-scripting/./geometry_generator.png" alt="" /></p>
<p>However, if any of the outputs is not <code>Geometry</code>, separate sockets are created for each output.</p>
<pre><code class="language-python">@tree(&quot;Primitive Shapes and Integer&quot;)
def primitive_shapes():
yield cube()
yield uv_sphere()
yield cylinder().mesh
yield 5 # Not a geometry socket type
</code></pre>
<p><img src="api/advanced-scripting/./mixed_generator.png" alt="" /></p>
<blockquote>
<p>The first output is always displayed when using a <em>Geometry Nodes</em> modifier. Ensure it is a <code>Geometry</code> socket type, unless you are using the function as a node group.</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="input-groups"><a class="header" href="#input-groups">Input Groups</a></h1>
<p>Some geometry node trees need a lot of arguments.</p>
<pre><code class="language-python">@tree(&quot;Terrain Generator&quot;)
def terrain_generator(
width: Float
height: Float
resolution: Int
scale: Float
w: Float
):
...
</code></pre>
<p>There are a couple of problems with this. Firstly, the function signature is getting long. This can make it harder to visually parse the script. And, if we want to use the same arguments in another tree and pass them through to <code>terrain</code>, we need to make sure to keep everything up to date.</p>
<p>This is where input groups come in. An input group is class that contains properties annotated with valid socket types.</p>
<p>To create an input group, declare a new class that derives from <code>InputGroup</code>.</p>
<pre><code class="language-python">class TerrainInputs(InputGroup):
width: Float
height: Float
resolution: Int
scale: Float
w: Float
</code></pre>
<p>Then annotate an argument in your tree function with this class.</p>
<pre><code class="language-python">@tree(&quot;Terrain Generator&quot;)
def terrain_generator(
inputs: TerrainInputs
):
...
</code></pre>
<p>This will create a node tree with the exact same structure as the original implementation. The inputs can be accessed with dot notation.</p>
<pre><code class="language-python">size = combine_xyz(x=input.width, y=input.height)
</code></pre>
<p>And now passing the inputs through from another function is much simpler.</p>
<pre><code class="language-python">def point_terrain(
terrain_inputs: TerrainInputs,
radius: Float
):
return terrain_generator(
inputs=terrain_inputs
).mesh_to_points(radius=radius)
</code></pre>
<h2 id="instantiating-input-groups"><a class="header" href="#instantiating-input-groups">Instantiating Input Groups</a></h2>
<p>If you nest calls to tree functions, you can instantiate the <code>InputGroup</code> subclass to pass the correct inputs.</p>
<pre><code class="language-python">def point_terrain():
return terrain_generator(
inputs=TerrainInputs(
width=5,
height=5,
resolution=10,
scale=1,
w=0
)
).mesh_to_points()
</code></pre>
<h2 id="input-group-prefix"><a class="header" href="#input-group-prefix">Input Group Prefix</a></h2>
<p>If you use the same <code>InputGroup</code> multiple times, you need to provide a prefix. Otherwise, inputs with duplicate names will be created on your tree.</p>
<p>To do this, use square brackets next to the annotation with a string for the prefix.</p>
<pre><code class="language-python">def mountain_or_canyon(
mountain_inputs: TerrainInputs[&quot;Mountain&quot;], # Prefixed with 'Mountain'
canyon_inputs: TerrainInputs[&quot;Canyon&quot;], # Prefixed with 'Canyon'
is_mountain: Bool
):
return terrain_generator(
inputs=switch(switch=is_mountain, true=mountain_inputs, false=canyon_inputs)
)
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="attributes"><a class="header" href="#attributes">Attributes</a></h1>
<p>An important concept in Geometry Nodes is attributes. Many trees capture attributes or transfer them from one domain to another.</p>
<p>When using these methods, the <code>data_type</code> argument must be correctly specified for the transfer to work as intended.</p>
<pre><code class="language-python">@tree(&quot;Skin&quot;)
def skin():
# Create a cube
c = cube()
# Create a sphere
sphere = uv_sphere()
# Transfer the position to the sphere
transferred_position = c.transfer_attribute(
data_type=TransferAttribute.DataType.FLOAT_VECTOR,
attribute=position()
)
# Make the sphere conform to the shape of the cube
return sphere.set_position(position=transferred_position)
</code></pre>
<p>To improve the usability of these nodes, <code>capture(...)</code> and <code>transfer(...)</code> methods are provided on <code>Geometry</code> that simply take the attribute and any other optional arguments.</p>
<pre><code class="language-python">@tree(&quot;Skin&quot;)
def skin():
# Create a cube
c = cube()
# Create a sphere
sphere = uv_sphere()
# Make the sphere conform to the shape of the cube
return sphere.set_position(position=c.transfer(position()))
</code></pre>
<p>The same is available for <code>capture(...)</code>.</p>
<pre><code class="language-python">geometry_with_attribute, attribute = c.capture(position())
</code></pre>
<blockquote>
<p>You must use the <code>Geometry</code> returned from <code>capture(...)</code> for the anonymous attribute it creates to be usable.</p>
</blockquote>
<p>Any additional keyword arguments can be passed as normal.</p>
<pre><code class="language-python">c.transfer(position(), mapping=TransferAttribute.Mapping.INDEX)
</code></pre>
<h2 id="named-attributes"><a class="header" href="#named-attributes">Named Attributes</a></h2>
<p>Custom attributes can be created by name.
The safest way to use named attributes is with the <code>Attribute</code> class.</p>
<p>Create a named attribute with a data type and optional domain, then use the <code>store(...)</code>, <code>exists()</code>, and <code>__call__(...)</code> methods to use it.</p>
<pre><code class="language-python"># Create the attribute
my_custom_attribute = Attribute(
&quot;my_custom_attribute&quot;,
NamedAttribute.DataType.FLOAT, # declare the data type once
StoreNamedAttribute.Domain.INSTANCE # optional
)
# Store a value
geometry = my_custom_attribute.store(geometry, 0.5)
# Use the value by calling the attribute
geometry = geometry.set_position(offset=my_custom_attribute())
</code></pre>
<h2 id="attribute-sampling"><a class="header" href="#attribute-sampling">Attribute Sampling</a></h2>
<p>In Blender 3.4+, transfer attribute was replaced with a few separate nodes: <em>Sample Index</em>, <em>Sample Nearest</em>, and <em>Sample Nearest Surface</em>.</p>
<p>To avoid inputting data types and geometry manually, you can use the custom <code>Geometry</code> subscript.</p>
<p>The structure for these subscripts is:</p>
<pre><code class="language-python">geometry[value : index or sample position : domain, mode, domain]
</code></pre>
<p>Only the value argument is required. Other arguments can be supplied as needed.</p>
<pre><code class="language-python">geometry[value]
geometry[value : sample_position, SampleMode.NEAREST]
geometry[value : index() + 1 : SampleIndex.Domain.EDGE]
</code></pre>
<p>Try passing different arguments and see how the resulting nodes are created.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="boolean-math"><a class="header" href="#boolean-math">Boolean Math</a></h1>
<p>The <em>Boolean Math</em> node gives access to common boolean operations, such as <code>AND</code>, <code>NOT</code>, <code>XOR</code>, etc.</p>
<p>However, it can be cumbersome to use the <code>boolean_math</code> function in complex boolean expressions.</p>
<pre><code class="language-python"># Check if the two values equal, or if the first is true.
x = False
y = True
return boolean_math(
operation=BooleanMath.Operation.OR
boolean=(
boolean_math(
operation=BooleanMath.Operation.XNOR # Equal
boolean=(x, y)
),
x
)
)
</code></pre>
<p>A few operators are available to make boolean math easier and more readable.</p>
<pre><code class="language-python"># Check if the two values equal, or if the first is true.
x = False
y = True
return (x == y) | x
</code></pre>
<p>The operators available are:</p>
<ul>
<li><code>==</code> - <code>XNOR</code></li>
<li><code>!=</code> - <code>XOR</code></li>
<li><code>|</code> - <code>OR</code></li>
<li><code>&amp;</code> - <code>AND</code></li>
<li><code>~</code> - <code>NOT</code></li>
</ul>
<blockquote>
<p>You <em>cannot</em> use the built-in Python keywords <code>and</code>, <code>or</code>, and <code>not</code>. You must use the custom operators above to create <em>Boolean Math</em> nodes.</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="drivers"><a class="header" href="#drivers">Drivers</a></h1>
<p>Drivers can be used with geometry nodes. To create a scripted expression driver, use the <code>scripted_expression</code> convenience function.</p>
<p>This can be used to get information like the current frame number in a Geometry Script.</p>
<pre><code class="language-python">frame_number = scripted_expression(&quot;frame&quot;)
frame_number_doubled = scripted_expression(&quot;frame * 2&quot;)
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="simulation"><a class="header" href="#simulation">Simulation</a></h1>
<blockquote>
<p>This API is subject to change as future builds of Blender with simulation nodes are released.</p>
</blockquote>
<p>The <code>geometry-nodes-simulation</code> branch of Blender 3.5 includes support for &quot;simulation nodes&quot;.</p>
<p>Using a <em>Simulation Input</em> and <em>Simulation Output</em> node, you can create effects that change over time.</p>
<p>As a convenience, the <code>@simulation</code> decorator is provided to make simulation node blocks easier to create.</p>
<pre><code class="language-python">@simulation
def move_over_time(
geometry: Geometry, # the first input must be `Geometry`
speed: Float,
dt: SimulationInput.DeltaTime, # Automatically passes the delta time on any argument annotated with `SimulationInput.DeltaTime`.
elapsed: SimulationInput.ElapsedTime, # Automatically passes the elapsed time
) -&gt; Geometry:
return geometry.set_position(
offset=combine_xyz(x=speed)
)
</code></pre>
<p>Every frame the argument <code>geometry</code> will be set to the geometry from the previous frame. This allows the offset to accumulate over time.</p>
<p>The <code>SimulationInput.DeltaTime</code>/<code>SimulationInput.ElapsedTime</code> types mark arguments that should be given the outputs from the <em>Simulation Input</em> node.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="voxelize"><a class="header" href="#voxelize">Voxelize</a></h1>
<p>This tutorial walks you through creating a script that turns any mesh into voxels.</p>
<blockquote>
<p>This tutorial requires Blender 3.4+ for the <em>Distribute Points In Volume</em> node.</p>
</blockquote>
<h2 id="setting-up"><a class="header" href="#setting-up">Setting Up</a></h2>
<p>Create a base mesh. I'll be using a Monkey primitive.</p>
<p><img src="tutorials/./monkey.png" alt="" /></p>
<p>Next, create a new script. Setting up an <a href="tutorials/../setup/external-editing.html">external editor</a> is recommended.</p>
<p>Import Geometry Script, and create a basic tree builder function. We'll add a <code>geometry</code> argument and annotate it with the <code>Geometry</code> type to receive our base mesh (in this case, a monkey).</p>
<pre><code class="language-python">from geometry_script import *
@tree(&quot;Voxelize&quot;)
def voxelize(geometry: Geometry):
return geometry
</code></pre>
<p>Run the script to create the tree, then add a <em>Geometry Nodes</em> modifier to your mesh and select the <em>Voxelize</em> node group.</p>
<p><img src="tutorials/./voxelize_modifier.png" alt="" /></p>
<h2 id="arguments"><a class="header" href="#arguments">Arguments</a></h2>
<p>Add a new argument <code>resolution: Float</code>. Give it a default value of <code>0.2</code>. This value will be used throughout the script to configure spacing and voxel density.</p>
<pre><code class="language-python">def voxelize(geometry: Geometry, resolution: Float = 0.2):
...
</code></pre>
<h2 id="mesh-to-volume"><a class="header" href="#mesh-to-volume">Mesh to Volume</a></h2>
<p>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.</p>
<p>Use the <code>mesh_to_volume</code> function on the base mesh to convert it to a volume.</p>
<pre><code class="language-python">def voxelize(geometry: Geometry, resolution: Float = 0.2):
return geometry.mesh_to_volume( # Hollow mesh volume
interior_band_width=resolution,
fill_volume=False
)
</code></pre>
<p><img src="tutorials/./monkey_volume.png" alt="" /></p>
<h2 id="volume-to-points"><a class="header" href="#volume-to-points">Volume to Points</a></h2>
<p>Next, we need to create points to instance each voxel cube on. Use <code>distribute_points_in_volume</code> with the mode set to <code>DENSITY_GRID</code> to create a uniform distribution of points.</p>
<pre><code class="language-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=DistributePointsInVolume.Mode.DENSITY_GRID,
spacing=resolution
)
</code></pre>
<p><img src="tutorials/./monkey_points.png" alt="" /></p>
<h2 id="instance-cubes"><a class="header" href="#instance-cubes">Instance Cubes</a></h2>
<p>Finally, use <code>instance_on_points</code> with a cube of size <code>resolution</code> to instance a cube on each point created from our mesh.</p>
<pre><code class="language-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=DistributePointsInVolume.Mode.DENSITY_GRID,
spacing=resolution
).instance_on_points( # Cube instancing
instance=cube(size=resolution)
)
</code></pre>
<p><img src="tutorials/./monkey_voxels.png" alt="" /></p>
<p>You can lower the resolution to get smaller, more detailed voxels, or raise it to get larger voxels.</p>
<h2 id="final-script"><a class="header" href="#final-script">Final Script</a></h2>
<pre><code class="language-python"># NOTE: This example requires Blender 3.4+
from geometry_script import *
@tree(&quot;Voxelize&quot;)
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=DistributePointsInVolume.Mode.DENSITY_GRID,
spacing=resolution
).instance_on_points(
instance=cube(size=resolution)
)
</code></pre>
<h2 id="generated-node-tree-1"><a class="header" href="#generated-node-tree-1">Generated Node Tree</a></h2>
<p><img src="tutorials/./voxelize_nodes.png" alt="" /></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="city-builder"><a class="header" href="#city-builder">City Builder</a></h1>
<p>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 <a href="tutorials/../api/advanced-scripting/generators.html">generator</a> to combine the buildings with the roads.</p>
<p><img src="tutorials/./city_builder.gif" alt="" /></p>
<h2 id="setting-up-1"><a class="header" href="#setting-up-1">Setting Up</a></h2>
<p>Create a Bezier Curve object. You can enter edit mode and delete the default curve it creates.</p>
<p>Then create a new script. Setting up an <a href="tutorials/../setup/external-editing.html">external editor</a> is recommended.</p>
<p>Import Geometry Script, and create a basic tree builder function. We'll add a few arguments to configure the buildings.</p>
<pre><code class="language-python">from geometry_script import *
@tree(&quot;City Builder&quot;)
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
</code></pre>
<p>Run the script to create the tree, then add a <em>Geometry Nodes</em> modifier to your curve object and select the <em>City Builder</em> node group.</p>
<h2 id="buildings"><a class="header" href="#buildings">Buildings</a></h2>
<p>Let's start with the buildings. We'll distribute points on a grid with <code>size_x</code> and <code>size_y</code>.</p>
<pre><code class="language-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
</code></pre>
<p>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.</p>
<pre><code class="language-python">def city_builder(...):
...
return building_points.instance_on_points(
instance=cube().transform(translation=(0, 0, 0.5)),
scale=random_value(data_type=RandomValue.DataType.FLOAT_VECTOR, min=building_size_min, max=building_size_max, seed=seed),
)
</code></pre>
<h2 id="roads"><a class="header" href="#roads">Roads</a></h2>
<p>Using <code>curve_to_mesh</code>, we can turn the input curve into a flat mesh. We'll use the <code>yield</code> keyword to join the curve mesh and the building mesh automatically. Change the <code>building_points.instance_on_points</code> line to use <code>yield</code> for this to work.</p>
<pre><code class="language-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(...)
</code></pre>
<p>But now the buildings are overlapping the road. We need to remove any point that falls within the road curve. We'll use <code>geometry_proximity</code> and <code>delete_geometry</code> to find and remove these invalid points.</p>
<pre><code class="language-python">def city_builder(...):
...
building_points = ...
road_points = geometry.curve_to_points(mode=CurveToPoints.Mode.EVALUATED).points
building_points = building_points.delete_geometry(
domain=DeleteGeometry.Domain.POINT,
selection=geometry_proximity(target_element=GeometryProximity.TargetElement.POINTS, target=road_points, source_position=position()).distance &lt; road_width
)
...
</code></pre>
<h2 id="drawing-roads"><a class="header" href="#drawing-roads">Drawing Roads</a></h2>
<p>Enter edit mode and select the <em>Draw</em> tool. Simply draw roads onto your city to see the buildings and meshes update.</p>
<p><img src="tutorials/./city_builder.gif" alt="" /></p>
<h2 id="final-script-1"><a class="header" href="#final-script-1">Final Script</a></h2>
<pre><code class="language-python">from geometry_script import *
@tree(&quot;City Builder&quot;)
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=CurveToPoints.Mode.EVALUATED).points
# Delete points within the curve
building_points = building_points.delete_geometry(
domain=DeleteGeometry.Domain.POINT,
selection=geometry_proximity(target_element=GeometryProximity.TargetElement.POINTS, target=road_points, source_position=position()).distance &lt; road_width
)
# Building instances
yield building_points.instance_on_points(
instance=cube().transform(translation=(0, 0, 0.5)),
scale=random_value(data_type=RandomValue.DataType.FLOAT_VECTOR, min=building_size_min, max=building_size_max, seed=seed),
)
</code></pre>
<h2 id="generated-node-tree-2"><a class="header" href="#generated-node-tree-2">Generated Node Tree</a></h2>
<p><img src="tutorials/./city_builder_nodes.png" alt="" /></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</div>
</body>
</html>