Skip to content
July 22, 2010 / stevebaer

StarMaker – An advanced sample

starmaker

Here’s a sample that demonstrates quite a few of the advanced features in Rhino.python. This sample was written and tested on Windows (I doubt it is going to work very well on OSX given that Windows.Forms are being used.) You are going to need to be running at least the July 20th build of Rhino 5 to test this.

StarMaker is composed of two script files; starmaker.py and sliderform.py. Save these files to the same directory.

What does it do? StarMaker just creates a new curve based on points from a closed planar polyline and some input from a form. Run the Polygon command and create a polygon with something like 30 edges. Then run starmaker.py

Some python features demonstrated:

  1. Multiple Script Files – the python script is broken into two files. sliderform.py doesn’t do anything on it’s own and is imported into starmaker.py
  2. Direct use of RhinoCommon – the script uses some of the functionality provided by the rhinoscript python functions, but it really makes heavy use of .NET classes defined in RhinoCommon
  3. Custom Windows Form – This is pretty cool!!! sliderform.py is a custom Wnidows Form user interface. This file was actually created in SharpDevelop (http://www.icsharpcode.net/opensource/sd/). SharpDevelop has a nice user interface editor for creating custom forms which are written as python files. Try opening sliderform.py in SharpDevelop and click on the design button. (See the attached SharpDev image)
  4. Custom Display for Preview Geometry – The red curve, dotted lines, and points in the screenshot are not actual geometry in the rhino document but are drawn using display callbacks (also know as conduits)

Calling RhinoCommon from python hasn’t been covered too much on this site so much of the script may look foreign.

I want to thank Marc Fornes of http://www.theverymany.com for inspiring this sample.
sharpdev

The python scripts can be found here

import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *
class SliderForm(Form):
def __init__(self):
self.__value_changed_callback = None
self.InitializeComponent()
self.__disable_events = False
self._trackBar1.Value = 10
self._trackBar2.Value = 10
def InitializeComponent(self):
self._btnOk = System.Windows.Forms.Button()
self._btnCancel = System.Windows.Forms.Button()
self._lbl1 = System.Windows.Forms.Label()
self._trackBar1 = System.Windows.Forms.TrackBar()
self._txtBox1 = System.Windows.Forms.TextBox()
self._trackBar2 = System.Windows.Forms.TrackBar()
self._lbl2 = System.Windows.Forms.Label()
self._txtBox2 = System.Windows.Forms.TextBox()
self._trackBar1.BeginInit()
self._trackBar2.BeginInit()
self.SuspendLayout()
#
# btnOk
#
self._btnOk.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right
self._btnOk.DialogResult = System.Windows.Forms.DialogResult.OK
self._btnOk.Location = System.Drawing.Point(202, 98)
self._btnOk.Name = "btnOk"
self._btnOk.Size = System.Drawing.Size(75, 23)
self._btnOk.TabIndex = 6
self._btnOk.Text = "OK"
self._btnOk.UseVisualStyleBackColor = True
#
# btnCancel
#
self._btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right
self._btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
self._btnCancel.Location = System.Drawing.Point(283, 97)
self._btnCancel.Name = "btnCancel"
self._btnCancel.Size = System.Drawing.Size(75, 23)
self._btnCancel.TabIndex = 7
self._btnCancel.Text = "Cancel"
self._btnCancel.UseVisualStyleBackColor = True
#
# lbl1
#
self._lbl1.Location = System.Drawing.Point(12, 15)
self._lbl1.Name = "lbl1"
self._lbl1.Size = System.Drawing.Size(69, 23)
self._lbl1.TabIndex = 0
self._lbl1.Text = "Ratio (even)"
#
# trackBar1
#
self._trackBar1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right
self._trackBar1.AutoSize = False
self._trackBar1.Location = System.Drawing.Point(87, 12)
self._trackBar1.Maximum = 100
self._trackBar1.Name = "trackBar1"
self._trackBar1.Size = System.Drawing.Size(223, 30)
self._trackBar1.TabIndex = 1
self._trackBar1.TickStyle = System.Windows.Forms.TickStyle.None
self._trackBar1.ValueChanged += self.OnSliderValueChanged
#
# txtBox1
#
self._txtBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right
self._txtBox1.Location = System.Drawing.Point(316, 12)
self._txtBox1.Name = "txtBox1"
self._txtBox1.Size = System.Drawing.Size(42, 20)
self._txtBox1.TabIndex = 2
self._txtBox1.TextChanged += self.OnTextBoxChanged
#
# trackBar2
#
self._trackBar2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right
self._trackBar2.AutoSize = False
self._trackBar2.Location = System.Drawing.Point(87, 54)
self._trackBar2.Maximum = 100
self._trackBar2.Name = "trackBar2"
self._trackBar2.Size = System.Drawing.Size(223, 30)
self._trackBar2.TabIndex = 5
self._trackBar2.TickStyle = System.Windows.Forms.TickStyle.None
self._trackBar2.ValueChanged += self.OnSliderValueChanged
#
# lbl2
#
self._lbl2.Location = System.Drawing.Point(12, 57)
self._lbl2.Name = "lbl2"
self._lbl2.Size = System.Drawing.Size(69, 23)
self._lbl2.TabIndex = 3
self._lbl2.Text = "Ratio (odd)"
#
# m_txtBox2
#
self._txtBox2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right
self._txtBox2.Location = System.Drawing.Point(316, 54)
self._txtBox2.Name = "txtBox2"
self._txtBox2.Size = System.Drawing.Size(42, 20)
self._txtBox2.TabIndex = 4
self._txtBox2.TextChanged += self.OnTextBoxChanged
#
# SliderForm
#
self.AcceptButton = self._btnOk
self.CancelButton = self._btnCancel
self.ClientSize = System.Drawing.Size(370, 132)
self.Controls.Add(self._lbl1)
self.Controls.Add(self._trackBar1)
self.Controls.Add(self._txtBox1)
self.Controls.Add(self._lbl2)
self.Controls.Add(self._trackBar2)
self.Controls.Add(self._txtBox2)
self.Controls.Add(self._btnOk)
self.Controls.Add(self._btnCancel)
self.MaximizeBox = False
self.MinimizeBox = False
self.MinimumSize = System.Drawing.Size(300, 170)
self.Name = "SliderForm"
self.ShowInTaskbar = False
self.Text = "Slider Form"
self._trackBar1.EndInit()
self._trackBar2.EndInit()
self.ResumeLayout(False)
self.PerformLayout()
def OnSliderValueChanged(self, sender, e):
#__disable_events is used to keep from circular
#events being called
if( self.__disable_events!=True ):
self.__disable_events = True
self._txtBox1.Text = str(self._trackBar1.Value/10.0)
self._txtBox2.Text = str(self._trackBar2.Value/10.0)
self.__disable_events = False
if( self.__value_changed_callback!=None ):
val_even = self._trackBar1.Value/10.0
val_odd = self._trackBar2.Value/10.0
self.__value_changed_callback(val_even, val_odd)
def SetValueChangedCallback(self, callback_object):
self.__value_changed_callback = callback_object
def OnTextBoxChanged(self, sender, e):
#__disable_events is used to keep from circular
#events being called
if( self.__disable_events!=True ):
self.__disable_events = True
try:
self._trackBar1.Value = float(self._txtBox1.Text) * 10.0
self._trackBar2.Value = float(self._txtBox2.Text) * 10.0
except:
self._txtBox1.Text = str(self._trackBar1.Value/10.0)
self._txtBox2.Text = str(self._trackBar2.Value/10.0)
self.__disable_events = False

view raw
sliderform.py
hosted with ❤ by GitHub

import sliderform #SliderForm defined in SharpDevelop
import rhinoscriptsyntax as rs
import Rhino
import System.Drawing
import scriptcontext
import System.Windows.Forms
class SliderFormController():
"""Hook up UI of the SliderForm with some Rhino specific actions"""
def __init__(self, curve_ids, dialog):
# tell the SliderForm about a function to call when the slider values change
dialog.SetValueChangedCallback(self.UpdateTempCurves)
# Set up display conduit events to allow drawing of temporary preview
# geometry while the values are being adjusted by the user
#
# CalculateBoundingBox event is used to adjust the view's clipped bounds.
# Preview geometry may be outside the area of the bounding box of geometry
# in the document and therefore may be clipped if we don't adjust the bbox
Rhino.Display.DisplayPipeline.CalculateBoundingBox += self.OnCalcBoundingBox
# DrawForeground event is used to actually draw some temporary geometry on top
# of everything else. Depth writing/testing are off at this stage so all
# geometry is drawn on top
Rhino.Display.DisplayPipeline.DrawForeground += self.OnDrawForeground
# Get notified of when the form closes so we can remove our conduits
dialog.FormClosed += self.OnSliderFormClosed
# temp_curves and temp_curves_bbox are used for dynamic display
self.__temp_curves = []
self.__temp_curves_bbox = None
# original_curves is a list of (id, centroid, points) for each curve
# provided in the constructor. This is the information we need to
# quickly construct our temp_curves
self.__original_curves = []
for curve_id in curve_ids:
# curve_id is a Guid. Try to get the Curve geometry from this Guid
objref = Rhino.DocObjects.ObjRef(curve_id)
curve_geometry = objref.Curve()
objref.Dispose()
if( curve_geometry!=None and curve_geometry.IsClosed ):
mp = Rhino.Geometry.AreaMassProperties.Compute(curve_geometry)
rc, points = curve_geometry.TryGetPolyline()
if( rc ): self.__original_curves.append( (curve_id, mp.Centroid, points) )
def OnSliderFormClosed(self, sender, e):
# Form has closed. Remove our display conduit
Rhino.Display.DisplayPipeline.DrawForeground -= self.OnDrawForeground
Rhino.Display.DisplayPipeline.CalculateBoundingBox -= self.OnCalcBoundingBox
scriptcontext.doc.Views.Redraw()
def OnDrawForeground(self, sender, e):
# This function is called while updating viewports. Try not to perform
# too many calculations in here to minimize display performance hit.
# Draw each temp_curve in red and a "control" polygon in feedback color
curve_color = System.Drawing.Color.Red
feedback_color = Rhino.ApplicationSettings.AppearanceSettings.FeedbackColor
for points, curve in self.__temp_curves:
e.Display.DrawCurve(curve, curve_color, 1)
e.Display.DrawDottedPolyline( points, feedback_color, True )
e.Display.DrawPoints(points, Rhino.Display.PointStyle.ControlPoint, 2, feedback_color)
def OnCalcBoundingBox(self, sender, e):
# Update the boundingbox to include our temporary curves
if(self.__temp_curves_bbox!=None):
e.IncludeBoundingBox(self.__temp_curves_bbox)
def UpdateTempCurves(self, ratio_offset_even, ratio_offset_odd):
self.__temp_curves = []
self.__temp_curves_bbox=None
for id, centroid, cvs in self.__original_curves:
# "cook" up a temp_curve from the original curve data
temp_points = []
count = len(cvs)
for index, cv in enumerate(cvs):
vector = cv centroid
# use %2 for even/odd test
if( index%2 == 0 or index==(count1)):
vector *= ratio_offset_even
else:
vector *= ratio_offset_odd
point = centroid + vector
temp_points.append(point)
temp_curve = Rhino.Geometry.Curve.CreateControlPointCurve(temp_points, 3)
if( temp_curve!=None ):
self.__temp_curves.append((temp_points,temp_curve))
if( self.__temp_curves_bbox==None ):
self.__temp_curves_bbox = temp_curve.GetBoundingBox(False)
else:
self.__temp_curves_bbox = Rhino.Geometry.BoundingBox.Union( self.__temp_curves_bbox, temp_curve.GetBoundingBox(False))
scriptcontext.doc.Views.Redraw()
def AddCurvesToDocument(self):
# add the temp_curves to the document so they become "real" Rhino geometry
for points, curve in self.__temp_curves:
scriptcontext.doc.Objects.AddCurve(curve)
scriptcontext.doc.Views.Redraw()
def main_function():
"""
main function that runs for this script when it is run as __main__
Returns: True or False indicating success or failure
"""
# get a list of curves – use a custom filter function to
# not allow anything but closed polylines
def onlyclosedpolylines( rhino_object, geometry, component_index ):
if( isinstance(geometry, Rhino.Geometry.Curve) ):
if( geometry.IsPolyline() and geometry.IsClsed ): return True
return False
curves = rs.GetObjects("pick curves (polylines.planar.closed)", rs.filter.curve, custom_filter = onlyclosedpolylines)
if( curves!=None ):
# create the SliderForm and hook a "controller" object to it
dialog = sliderform.SliderForm()
controller = SliderFormController(curves, dialog)
# Show our dialog in a semi-modal way.
if( Rhino.UI.Dialogs.ShowSemiModal( dialog ) == System.Windows.Forms.DialogResult.OK ):
controller.AddCurvesToDocument()
return True
return False
if( __name__ == "__main__" ):
main_function()

view raw
starmaker.py
hosted with ❤ by GitHub

5 Comments

Leave a Comment
  1. Bradley rothenberg / Oct 8 2014 5:15 pm

    Steve,

    I was going through the sliderform & starmaker script you posted here, & had a question about the variable “e” that you use in most of the functions that access the gui window. What is this variable for? I understand self & sender, but e is less apparent.

    thanks!

    Bradley

    • stevebaer / Oct 8 2014 5:20 pm

      Most events in .NET follow the pattern of

      Event(object sender, EventArgs e);

      The event args are custom to each type of event that occurs.

  2. Bradley rothenberg / Oct 8 2014 6:17 pm

    is this what I should be reading to understand this:

    http://msdn.microsoft.com/en-us/library/edzehd2t(v=vs.110).aspx

  3. Bradley rothenberg / Oct 8 2014 8:03 pm

    Steve,

    thanks for responding so quickly! Another question is what is the reasoning behind using the display conduit ?? Is it that much faster to use the display conduit vs. re-writing actual geometry?

    thanks!

    Bradley

    • stevebaer / Oct 16 2014 6:36 pm

      Yes, display conduits are typically what you use for previewing geometry before you actually go ahead and add it to the document

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: