Python in DrawScript
This tutorial is intended to give you the skills necessary to:
Execute Python code within a rule sequence or DrawScript that can dynamically change the program’s behavior
Use evaluatable expressions in rule sequence and DrawScript rule parameters
Save the results of a query to a Python variable
Manipulate saved query results
Complete DrawScript Template
.Tutorial 7.3dm
.Warning
The Python integration within rule sequences and DrawScript is still very new. You’ll likely have to create a lot of functionality if you want something particularly advanced.
See Also
The dedicated DrawScript-Python Reference Page
Part 1: Introducing DrawScript-Python
From it’s inception, DrawScript was designed to be a Turing-Complete programming language that bridges the computability gap in Shape Machine. Its visual nature allows for powerful, intuitive implementations of certain problems, but while geometric computation excels at certain tasks, it falls short for others. If one were able to leverage the strengths of both geometric and classical computation, the expressive power of DrawScript would be incredible. Enter: Python.
Shape Machine for Rhino is written in Python, and DrawScript is interpreted on demand by the Python program. The jump over to evaluating and executing Python code within DrawScript itself is therefore fairly trivial. This comes in two parts: dynamic execution and dynamic evaluation.
Dynamic Execution
Evaluating and executing Python code within DrawScript first requires that variables can be made, utilities can be imported, and functions can be defined, each for use across the entire DrawScript program. This is facilitated by providing the program with a context, a mapping from variable/function/etc. names to values. When something is imported or defined, it updates the context, and the updated context is later used when evaluating or executing Python code later in the program.
Dynamic execution is performed with the introduction of a new Python rule created for
DrawScript. The only thing present in the Python rule is a Text
element in the
python
layer. The text inside is any Python code you would like. When Shape Machine
runes the Python rule, it executes this code using the builtin
exec()
, providing the context as the globals
parameter.
Glossing over the nitty-gritty details, the important thing to know is that you can do whatever you’d like in a Python rule and take advantage of the side effects later within your DrawScript program.
Dynamic Evaluation
DrawScript has always been dynamically evaluated, but prior to DrawScript-Python, the parameters for each rule were evaluated at compile-time. With the introduction of DrawScript-Python, parameters are now evaluated dynamically, right before the rest of the rule is evaluated. More specifically, these parameters are evaluated as Python expressions.
This evaluation is performed using the same global context used for dynamic execution
of Python rules. This means that you could create a variable, say x
, in a
Python rule, and use it in the Loop
parameter of one of your DrawScript rules. The
parameters are reevaluated every time Shape Machine returns to the rule, which means
if you change x
, the second time Shape Machine executes the rule, it will
loop a different amount of times.

Example of dynamic expression followed by dynamic evaluation. Above the line is the
bottom of a Python rule that sets a variable x
to 10
. In the subsequent rule,
the Loop
parameter is set to x
. This has the effect of setting Loop=10
once evaluated.
Warning
Parameter evaluation is performed every time a rule loops, but the Loop
parameter
is evaluated only once when Shape Machine reaches a rule, in order to determine
how many times to loop. This means that, for example, if you use the random
module to randomize the Angle
parameter, it will be different every iteration of the loop,
but randomizing the Loop
parameter will not cause the number of loops to change
in each iteration of the loop.
Implications
Dynamic execution and evaluation of Python code in DrawScript has powerful implications. The following is a by-no-means-exhaustive list of things you can now do with DrawScript-Python:
Dynamically compute any number and use it in the
Scale
orAngle
parametersIf you use this with a replacement rule with a single point in the query, you can use these parameters to set the output scale and angle to something you compute with Python.
Use ternary statements (
x if statement else y
) in theLoop
parameter to easily control which rules get executedExecute a preamble Python rule at the start of the DrawScript program
A preamble rule is one that precedes every other rule in the program, allowing you to define functions and variables that will be used throughout the entire program.
Use a preamble rule to provide the user of your program with an easy way to configure parameters of the program
Create your own popups that can either provide information or request information during the execution of your program
Take advantage of any Python library
The possibilities are as extensive as your imagination!
Part 2: Exercises
Exercise 1: Hello, DrawScript-Python!
In this exercise, we’ll use the classic example of printing a string to understand how to edit DrawScript-Python scripts. The scaffold for this exercise provided in the tutorial Rhino file already has a statement that prints to the console. We will simply be modifying that statement.
Step-by-Step Guide
Modify the Provided DrawScript-Python
Change the content of the print statement to output “Hello, DrawScript-Python!”
Key Points
If you delete all the text in a
Text
element and leave the editor, you will have to create a newText
element in thepython
layer.The font family, size, style, etc. of DrawScript-Python blocks can be whatever you want.
Print statements output to the command history bar.
Exercise 2: Randomly-Nested Squares
In this exercise, we’ll return to the nested squares, this time using a randomized loop count to determine how many times to nest them.
Step-by-Step Guide
Import the
random
ModulePaste the following code into the
Text
element in the first Python rule:
from random import randint
x = randint(1, 6)
Create the Nesting Rule
Search for a black square under similarity, replacing it with a green square with a black square nested inside.
Set the
Loop
parameter tox
Of course, because the
Loop
parameter is evaluated dynamically, you could set it directly torandint(1, 6)
.
Convert All Green Lines to Black Lines
Search for green lines under similarity, replacing all of them with black lines.
Key Points
You can use variables and expressions within DrawScript rule parameters.
Anything you use has to be defined either in a Python rule or included by default.
Setting a variable to a random number lets it be used multiple times, each time providing the same number. If you instead call a random number function inside of your parameters, the value provided to the parameter will be different every evaluation.
Example: Boolean-Controlled Parameters
In this example, we’ll see how ternary expressions can be used in the Loop
parameter
to create highly controlled behavior.
This example is composed of 3 rules: one Python rule and two replacement rules. The first
replacement rule simply colors a black point pink, the other turns it blue. Which one
executes is determined by a variable defined in the Python rule: turnPink
.
Looking at the Loop
parameter in the first rule, we can see it says 1 if turnPink else 0
.
The second rule has the opposite: 1 if not turnPink else 0
. The text here can visually
extend beyond the boundary of the DrawScript block, but as long as the centerpoint of the
Text
element is within the boundary, this is okay.
Try running the DrawScript with turnPink = True
in the Python rule, then with
turnPink = False
.
Key Points
Booleans can provide highly granular control over your program.
Ternary expressions can be used in any parameter, allowing you to control loop counts, transformation types, selection modes, etc.
Python rules can be used to define boolean variables. You could use this in tandem with jump rules to ensure that only one jump in a set of jumps is taken.
Example: Requesting Input From a User
This example shows two ways to request input from a user. It’s based on nested squares, but the user is asked to specify how many times to loop the nesting.
Method 1: Command Prompt
The first way to request feedback is using the command prompt within Rhino. To test this
method, set popup = False
in the first Python rule of this program.
Note
Input of this variety can be accessed either by using methods provided under
the communication_layer
variable (definition) or
through RhinoScriptSyntax.
Source Code
x = communication_layer.get_user_int(
"How many times should the nesting be looped?",
1,
1,
10,
)
Method 2: Popup
The second way to request feedback is with popups. To test this method, set popup = True
in the first Python rule of this program.
Note
If you’re interested in learning more about creating popups, Rhino uses the Eto library for this.
The goal of this example is not to teach you how to use this (it’s kind of a pain), it is instead simply to encourage creativity.
Source Code
import Eto.Drawing as drawing
import Eto.Forms as forms
import Rhino
import System
x = 0
def do_popup():
global x
popup = forms.Dialog()
popup.BackgroundColor = drawing.Color(
0.7450980392, 0.7450980392, 0.7450980392, 1
)
popup.Title = "Enter Loop Count"
popup.Padding = drawing.Padding(5)
popup.Resizable = False
popup.Topmost = True
popup.Maximizable = False
popup.Minimizable = False
popup.WindowStyle = forms.WindowStyle.NONE
popup.MovableByWindowBackground = True
popup.ShowInTaskbar = True
description = forms.Label()
description.Text = "Please enter a number to indicate how many times the nesting operation should loop."
header = forms.Label()
header.Text = "How Many Times Should Nesting Loop?"
header.Font = drawing.Font(
description.Font.Family,
description.Font.Size + 4,
drawing.FontStyle.Bold,
)
input_label = forms.Label()
input_label.Text = "Loop count:"
input = forms.TextBox()
input.PlaceholderText = "Enter a number"
ok_button = forms.Button()
ok_button.Text = "Ok"
ok_button.Click += lambda *args, **kwargs: popup.Close()
layout = forms.DynamicLayout()
layout.Spacing = drawing.Size(10, 10)
layout.AddRow(header)
layout.AddRow(description)
layout.AddRow(None)
layout.AddRow(input_label, input)
layout.AddRow(ok_button)
popup.Content = layout
popup.ShowModal()
x = int(input.Text)
# Forms need to be built and shown on the UI thread, but DrawScript runs on an
# asynchronous thread. This means we need to do the following to keep the threads
# happy.
Rhino.RhinoApp.InvokeAndWait(System.Action(do_popup))
Part 3: Saving Queries to Variables
In addition to executable Python rules, DrawScript-Python also supports the ability of saving shape queries to a variable. With a query result saved in a variable, you can do whatever you want with geometry the Shape Machine finds within your designs.
This behavior is facilitated with save-search rules. The LHS is identical to what you’d
have in a replacement rule, instructing Shape Machine what to search for. The RHS is a
single Text
element that allows you to specify the name of the variable to save
the search results to.

The results of the search will be provided in one of two ways. If selection mode 1
is
used (or 3
followed by selecting a single match), the results will be saved as a
single shapemachine.geometry.shape.Shape
. If, instead,
selection mode 2
is used (or 3
followed by selecting all matches), the results will
be saved as a list
of
shapemachine.geometry.shape.Shape
s. If no matches
are found, the results will either be saved as an empty
shapemachine.geometry.shape.Shape
or an empty
list
, based on the selection mode.
Warning
Taking advantage of save-search rules requires familiarity with Shape Machine’s Python API, found here.
There will be no dedicated exercises taking advantage of the save-search rules, but the following example shows a very simple use case that you can imagine could be extended further.
Example: Finding the Closest Two Points to Another Point
With save-search rules, you can find one point, then find the two points that are closest to it. The two closest points are changed to be red. It’s a fairly mundane example, but you can hopefully imagine how this sort of thing might be useful, perhaps even in tandem with mark-intersection rules.
Play around with the example, move the points around, and see how the program behaves.
Source Code
import numpy as np
from shapemachine.geometry.shape import Shape
from shapemachine.geometry.point import Point
# In this case, target is a Shape, and points is a list of Shapes.
# as such, we have access to the center attribute. Because these shapes
# are all single points, this, in effect, works to get the location of the point.
def distance_to_target(shape):
return np.linalg.norm(target.center - shape.center)
# Sort the list of points by proximity to the target point
sorted_points = sorted(points, key=distance_to_target)
# Unpack the closest two points
closest, second_closest = sorted_points[:2]
# Get the attributes of the Red layer
red_attr = make_attributes("Red")
# The remaining steps simply replace the two closest black points
# with red versions, keeping them usable for the engine in the
# final replacement rule.
# This is done in two parts: updating the engine's current design,
# which allows Shape Machine to use the geometry later, and replacing
# the current design in the communication layer, which updates what you
# see in Rhino.
engine.current_design -= closest
engine.current_design -= second_closest
engine.current_design += Shape(
[],
[],
[
Point(closest.center, red_attr),
Point(second_closest.center, red_attr)
]
)
communication_layer.replace_current_design(engine.current_design, engine.to_design)\
Part 4: Conclusion
In this tutorial, you learned:
Python Rules: A new type of rule in DrawScript that allows you to dynamically execute Python code within a DrawScript program.
Dynamic Parameter Evaluation: DrawScript parameters are evaluated dynamically each time Shape Machine executes the rule. The
Loop
parameter is only evaluated once per rule per block.Save-Search Rule: A new type of rule in DrawScript that allows you to search for a shape and save the search result(s) to a variable, for use in a Python rule or parameter evaluation.
Additional Resources: Access additional resources for DrawScript-Python via the dedicated page: DrawScript-Python Reference