YES!!! This could lead to some incredible applications.
http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/
Now if they only had a version that clipped to your monitor and tracked your fingers…
I received a question on accessing the ShapeWays API from my last “Outside the Box” blog and figured I should dig in and figure out what is going on. Shapeways has a web service API that uses SOAP and a WSDL page (http://en.wikipedia.org/wiki/Web_Services_Description_Language) as described here
http://www.shapeways.com/api
This API is a bit different than the REST API which I wrote a script to access in my previous blog post. In order to use this API, I ended up modifying a script originally put together by Michael Foord (author of “IronPython in Action”) that creates a .NET assembly on the fly for a given WSDL url.
The two scripts are in gists at the end of this post
Place both of these scripts in the same directory and open the “useshapeways.py” script. Here’s the script itself
"""Sample script that accesses the shapeways API [url]http://www.shapeways.com/api[/url] """ import wsdlprovider wsdl_url = "http://api.shapeways.com/v1/wsdl.php" username = "username" password = "password" application_id = "rhinotest" assembly = wsdlprovider.GetWebservice(wsdl_url) shapeways = assembly.SWwsdlService() session_id = shapeways.login(username, password, application_id) if session_id: #get list of printers available printers = shapeways.getPrinters(session_id, "", application_id) if printers: for printer in printers: print "printer:", printer.title for material in printer.materials: print " - material ", material.title
The script uses the wsdlprovider script to generate a .NET assembly from the shapeways wsdl url. This assembly has a class in it called SWwsdlService which we create an instance of and call functions on. It looks like a normal class to python, but all of the function calls are sent to ShapeWays over the internet and response are turned into classes that you can use. This sample simply logs into shapeways to get a “session id” and then asks Shapeways for a list of it’s available printers along with what materials each printer supports.
At the time of this blog post, the printed output from this script is
printer: Somatech FDM
- material Grey Robust
printer: Somatech Objet 720
- material Black Detail
- material White Detail
- material Transparent Detail
printer: SLS Printer
- material White Strong & Flexible
printer: Metal Printer matt
- material Gold Plated Glossy
- material Antique Bronze Glossy
- material Antique Bronze Matte
- material Stainless Steel
printer: SLS Color Printer
- material Black Strong & Flexible
printer: Silver Printer
- material Silver Glossy
- material Silver
printer: ZPrinter 650
- material Sandstone
- material Full Color Sandstone
printer: SLS Alumide
- material Alumide
printer: Glass Printer
- material High Gloss Black Glass
- material High Gloss White Glass
- material Milky White Matte Glass
printer: Metal printer Gold
- material Gold Plated Matte
printer: SLS Color Printer New
- material Dark Grey Strong and Flexible
- material Indigo Strong and Flexible
- material Winter Red Strong and Flexible
printer: HD printer
- material Frosted Detail
printer: UHD printer
- material Frosted Ultra Detail
printer: SLS Printer polished
- material White Strong & Flexible Polished
printer: Ceramics printer
- material Glazed Ceramics
Pretty neat!
"""Sample script that accesses the shapeways API | |
http://www.shapeways.com/api | |
""" | |
import wsdlprovider | |
wsdl_url = "http://api.shapeways.com/v1/wsdl.php" | |
username = "username" | |
password = "password" | |
application_id = "rhinotest" | |
assembly = wsdlprovider.GetWebservice(wsdl_url) | |
shapeways = assembly.SWwsdlService() | |
session_id = shapeways.login(username, password, application_id) | |
if session_id: | |
#get list of printers available | |
printers = shapeways.getPrinters(session_id, "", application_id) | |
if printers: | |
for printer in printers: | |
print "printer:", printer.title | |
for material in printer.materials: | |
print " – material ", material.title |
''' | |
Generate a proxy class for a SOAP web service from its WSDL. | |
Based on C# implementation from the DynamicWebService IronPython example. | |
''' | |
# Original script from "IronPython in Action" Book code found at | |
# http://code.google.com/p/ironpython/ | |
# http://www.voidspace.org.uk/ironpython/ | |
# 2 June 2011 – S. Baer | |
# Modified to cache the generated assemblies for performance purposes | |
import clr | |
clr.AddReference("System.Web.Services") | |
clr.AddReference("System.Xml") | |
from System.Web.Services.Description import ( | |
ServiceDescription, ServiceDescriptionImporter | |
) | |
from System.Web.Services.Protocols import SoapHttpClientProtocol | |
from System.IO import MemoryStream | |
from System.Net import WebClient | |
from System.CodeDom import ( | |
CodeCompileUnit, CodeNamespace | |
) | |
from System.CodeDom.Compiler import CodeDomProvider, CompilerParameters | |
from System.Xml.Serialization import CodeGenerationOptions | |
def GetBytes(url): | |
'download the file at url' | |
return WebClient().DownloadData(url) | |
def CreateWebServiceFromWsdl(wsdl): | |
'convert the WSDL into an assembly containing the web service proxy classes' | |
# generate codeDom from wsdl | |
sd = ServiceDescription.Read(MemoryStream(wsdl)) | |
importer = ServiceDescriptionImporter() | |
importer.ServiceDescriptions.Add(sd) | |
codeCompileUnit = CodeCompileUnit() | |
codeNamespace = CodeNamespace("") | |
codeCompileUnit.Namespaces.Add(codeNamespace) | |
importer.CodeGenerationOptions = (CodeGenerationOptions.GenerateNewAsync | |
| CodeGenerationOptions.GenerateOldAsync) | |
importer.Import(codeNamespace, codeCompileUnit) | |
# compile CodeDom into an assembly | |
provider = CodeDomProvider.CreateProvider("CS") | |
compilerParams = CompilerParameters() | |
compilerParams.GenerateInMemory = True | |
compilerParams.IncludeDebugInformation = False | |
results = provider.CompileAssemblyFromDom(compilerParams, codeCompileUnit) | |
generatedAssembly = results.CompiledAssembly | |
return generatedAssembly | |
# dictionary used to cache assemblies generated from wsdl | |
__assembly_cache = {} | |
def GetWebservice(url): | |
key = url.lower() | |
if not __assembly_cache.has_key(key): | |
'download the WSDL for the service URL and generate an assembly from it' | |
if url.lower().endswith(".asmx"): url += "?WSDL" | |
data = GetBytes(url) | |
assembly = CreateWebServiceFromWsdl(data) | |
if not assembly: return None | |
__assembly_cache[key] = assembly | |
return __assembly_cache[key] | |
def FindProxyType(assembly): | |
"""if you aren't sure of the name of the proxy type that will be generated, | |
use this to find it""" | |
for name in dir(assembly): | |
attr = getattr(assembly, name) | |
if type(attr) is type and issubclass(attr, SoapHttpClientProtocol): | |
return attr |
[
One of the nice bits that we have access to in Rhino python is the Task Parallel Library that is built into .NET 4
http://msdn.microsoft.com/en-us/library/system.threading.tasks.aspx
This set of classes and functions makes it relatively easy to write things like parallel for loops in which every iteration of the loop may be processed on different threads. This nice thing about parallel for loops is that they make coding with multiple threads much simpler since the “multi-threading” only occurs inside the for loop and once the loop is finished you know that the all of the threads have completed and you are back on the main execution thread.
Here’s a sample python script which runs many Plane-Brep intersections either on a single thread or using multiple threads.
import System.Threading.Tasks as tasks | |
import Rhino | |
import rhinoscriptsyntax as rs | |
import time, math | |
import scriptcontext | |
def radial_contour(brep, parallel, slice_count=360): | |
"""Generate series of curve slices through a brep by rotating a plane | |
multiple times and intersecting that plane with the brep. This function | |
demonstrates the use of .NET Parallel.For in order to run the function | |
in parallel | |
Parameters: | |
brep = the Brep to contour | |
parallel = If True, this function will compute intersections in multiple | |
threads using Parallel.For. If False, all intersections will be performed | |
on a single thread | |
slice_count = number of slices to generate. Slices are evenly distributed | |
over a full circle | |
""" | |
if not brep: return | |
results = range(slice_count) | |
rotation_axis = Rhino.Geometry.Vector3d(0,1,0) | |
intersect_tol = scriptcontext.doc.ModelAbsoluteTolerance | |
# local function that does the intersection work. This function is called | |
# once for each angle in "slice_count" and needs to be thread-safe | |
def slice_brep_at_angle(i): | |
try: | |
angle_rad = i/slice_count * 2.0 * math.pi | |
plane = Rhino.Geometry.Plane.WorldXY | |
plane.Rotate(angle_rad, rotation_axis, Rhino.Geometry.Point3d.Origin) | |
rc, crvs, pts = Rhino.Geometry.Intersect.Intersection.BrepPlane(brep, plane, intersect_tol) | |
if rc: results[i] = crvs | |
else: results[i] = None | |
except: | |
pass | |
if parallel: | |
tasks.Parallel.ForEach(xrange(slice_count), slice_brep_at_angle) | |
else: | |
for i in xrange(slice_count): slice_brep_at_angle(i) | |
return results | |
if __name__=="__main__": | |
brep = rs.GetObject("Select Brep", rs.filter.polysurface) | |
brep = rs.coercebrep(brep) | |
if brep: | |
# Make sure the Brep is not under the control of the document. This is | |
# just done so we know we have a quick to access local copy of the brep | |
# and nothing else can interfere while performing calculations | |
brep.EnsurePrivateCopy() | |
#run the function on a sinlge thread | |
start = time.time() | |
slices1 = radial_contour(brep, False) | |
end = time.time() | |
print "serial = ", end–start | |
#run the function on mulitple threads | |
start = time.time() | |
slices2 = radial_contour(brep, True) | |
end = time.time() | |
print "parallel = ", end–start | |
if slices2: | |
for curveset in slices2: | |
if curveset: | |
for curve in curveset: scriptcontext.doc.Objects.AddCurve(curve) | |
scriptcontext.doc.Views.Redraw() |
I’ve been trying to learn more about things like web services and APIs provided by internet companies (gotta figure out what all of these buzzwords are.) One thing I’ve noticed is that many companies now provide a REST API which return JSON objects. There are enough resources on the web that describe REST and JSON that I don’t need to repeat it here; I’m just going to get to my python experiments on this technology.
So what does it do?
The python script uses the Google Translate API to translate text from one language to another. Maybe not the most useful new tool for Rhino, but it shows how simple it is to format a request to a web service and get at the results.
Some important things to note:
- The scripts in both attachments are exactly the same. There is code in the script to figure out if it is running as a normal Rhino python script or inside of Grasshopper.
- The script does different things when it is running in Rhino versus when running in Grasshopper. In Rhino, the script converts text dot text which in Grasshopper the script uses input and output variables.
- I’m using the sticky variable to cache results. This way we don’t have to keep going out to Google if we are translating the same text over and over again
Search around on the internet for REST apis that return JSON data. Your python scripts that use these other services will end up looking pretty similar to the samples I posted (unless you don’t like my scripting style;)) Here are some interesting APIs to look at:
http://code.google.com/more/
http://friendfeed.com/api/documentation
http://www.flickr.com/services/api/
http://developer.ning.com/docs/ningapi/1.0/index.html
http://develop.github.com/
http://api.pachube.com/
"""Use Google translate web service""" | |
import rhinoscriptsyntax as rs | |
import json, urllib | |
import scriptcontext | |
# Api key that I (Steve Baer) got from Google | |
# http://code.google.com/apis/console-help/#UsingKeys | |
# You might want to generate your own key, but I don't care | |
# if you continue to use this one. | |
KEY = "AIzaSyAZGoS-GjZGaSHZMZdoczfdUtWTjm_D-p4" | |
def translate(text, source="en", target="fr"): | |
"""Translate text from one language to another. Returns the | |
translated text on success or None on failure | |
""" | |
# see if we already have a cached answer | |
cache_dict_name = "translate " + source + ":" + target | |
if scriptcontext.sticky.has_key(cache_dict_name): | |
cache = scriptcontext.sticky[cache_dict_name] | |
if cache.has_key(text): return cache[text] | |
url = "https://www.googleapis.com/language/translate/v2" | |
url += "?key=" + KEY | |
url += "&q="+urllib.quote(text.encode('utf-8')) | |
url += "&source="+source | |
url += "&target="+target | |
f = urllib.urlopen(url) | |
s = f.read().decode('UTF-8') | |
# it seems that the google translate api return html encoded strings | |
htmlcodes = ('&', '&'),('<', '<'),('>', '>'),('"', '"'),("'", ''') | |
for c, code in htmlcodes: s = s.replace(code, c) | |
f.close() | |
rc = json.loads(s) | |
if rc.has_key("data"): | |
translated = rc["data"]["translations"][0]["translatedText"] | |
if not scriptcontext.sticky.has_key(cache_dict_name): | |
scriptcontext.sticky[cache_dict_name] = {} | |
scriptcontext.sticky[cache_dict_name][text] = translated | |
return translated | |
# use __name__ test to determine if this script is directly being | |
# executed or if it is being loaded as a library | |
if( __name__=="__main__" ): | |
if rs.ContextIsGrasshopper(): | |
# if this script is running in grasshopper, translate the input | |
# variable and set the output variables | |
spanish = translate(english, "en", "es") | |
german = translate(english, "en", "de") | |
french = translate(english, "en", "fr") | |
italian = translate(english, "en", "it") | |
japanese = translate(english, "en", "ja") | |
elif rs.ContextIsRhino(): | |
# get text dots and translate their contents | |
dots = rs.GetObjects("Select dots to translate", rs.filter.textdot) | |
if dots: | |
langs = { "English":"en", | |
"ChineseSimplified":"zh-CN", | |
"ChineseTraditional":"zh-TW", | |
"Czech":"cs", | |
"French":"fr", | |
"German":"de", | |
"Italian":"it", | |
"Japanese":"ja", | |
"Korean":"ko", | |
"Polish":"pl", | |
"Spanish":"es" | |
} | |
source_lang = rs.GetString("source", "English", langs.keys()) | |
source_lang = langs[source_lang] | |
target_lang = rs.GetString("target", "Spanish", langs.keys()) | |
target_lang = langs[target_lang] | |
for dot in dots: | |
s = rs.TextDotText(dot) | |
s = translate(s, source_lang, target_lang) | |
rs.TextDotText(dot, s) |
Giulio Piacentino and I have been working on an update to the grasshopper/python component and finally have something to show off!!!
http://www.food4rhino.com/project/ghpython – NOTE: Will only work in Rhino 5
This component is a scripting component similar to the VB.NET and C# components that ship with Grasshopper. You get a component where you can define as many input and output variables as you want and then reference these variables in the python script.
If you double click on the component, you get a light version of the python script editor which you are used to in the EditPythonScript command (keywords are colorized and functions autocomplete when possible)
But there is a cool twist… this component supports the rhinoscriptsyntax functions. The rhinoscriptsyntax functions can be set to generate geometry inside of Grasshopper that does not live in the Rhino document. We are using a concept called “duck typing” to swap the document that the rhinoscriptsyntax functions use from the rhino document to a grasshopper document. This means that the following script
import rhinoscriptsyntax as rs for x in range(10): rs.AddPoint((x,0,0)
will add 10 points to the Rhino document when run from Rhino’s “RunPythonScript” or “EditPythonScript” functions. The same script will add 10 points to a grasshopper document that can be passed on to other grasshopper components when run inside the syntax of grasshopper.
We are writing this as an open source project which is hosted at
https://github.com/mcneel/ghpython
Developers are welcome to download, compile, and tinker with the component source code. If you find bugs or want to add features, let us know and we’ll figure out how to get you more involved in the development process.
You can also log bugs and wishlist items at
https://github.com/mcneel/ghpython/issues
Use your python scripting skills in Rhino 5 for Windows, Rhino 5 for Mac, and in Grasshopper for Rhino 5;)
Thanks,
-Steve
This is only for Windows Rhino right now since you can’t customize toolbar buttons on Mac yet, but once that feature becomes available I’m pretty sure the technique will be the same.
In Rhino, create a new toolbar button and edit it. Consult the Rhino help for for adding and editing toolbar buttons. Now you have three options (that I can think of at the moment) for adding a python script to a toolbar button. All of these involve executing the RunPythonScript command
- Directly embed the script
Make sure the first line reads -_RunPythonScript (. Note that there needs to be a space between RunPythonScript and the parentheses.
Make sure the last line is just a closing parentheses. The lines in between are interpreted as your python script.-_RunPythonScript ( import rhinoscriptsyntax as rs point0 = rs.GetPoint("start") point1 = rs.GetPoint("end") rs.AddLine(point0,point1) )
- Absolute path to the script
-_RunPythonScript (C:\Users\a-steve\Desktop\CurveLength.py) - Path to script on search path
If the script is on python’s search path, you can use the following form to let python find the script (use period separate subdirectories.)
-_RunPythonScript (Samples.CurveLength.py)
Komodo Edit can be configured to recognize the rhinoscriptsyntax module which provides for editor features like autocomplete and function tooltips.
- Download and start Komodo Edit – http://www.activestate.com/komodo-edit
- On the menu, select Komodo->Preferences…
- Expand “Languages” and select “Python”
- In the Additional Python Import Directories click on the “add” button and navigate to
/Users//Application Support/McNeel/Rhinoceros/MacPlugIns/IronPython/settings/lib - Click OK, it may take a minute for Komodo Edit to parse all of the files and generate autocomplete information, but once that is done you should be able to type your Mac Rhino python scripts with a little help from Komodo!!
Finally, a plug-in architecture for Mac Rhino!!!
The python plug-in for Mac was our first experiment to see if we could actually get .NET plug-ins to work on Mac Rhino and support this framework on Mac for plug-in development. Well… with the latest build of Mac Rhino, I think we’ve got enough support in the application to start telling people how they can write and debug .NET plug-ins on the Mac.
In order to RUN .NET plug-ins on the Mac, you are going to need:
- The latest Mac Rhino build (at least build 2011-02-24) – http://mac.rhino3d.com/
- The latest distribution of Mono (2.10 at the time of this writing) – http://www.go-mono.com/mono-downloads/download.html
For mono, download the Intel Framework
With the above two items installed, you can test to see if everything is working by installing the python plug-in for Mac Rhino
http://wiki.mcneel.com/rhino/mac/python
If you can run the command “RunPythonScript”, then Mac Rhino is properly configured to use .NET plug-ins!!
Ok, so how does it work???
The python plug-in that you installed has a “.macrhi” file extension. This is really just a zip file that has had the file extension changed so Mac Rhino knows how to deal with it. Try changing the .macrhi file extension back to .zip and looking at the contents in finder.
You’ll notice that the zip file contain a single directory named IronPython with a bunch of dll, xml, and rhp files. The RhinoDLR_Python.rhp file is a .NET Rhino plug-in that was built against RhinoCommon. This rhp is loaded by Mac Rhino in a similar fashion as Windows Rhino. When you double click on a macrhi, Rhino unzips the contents of this file to a specific directory on your computer; specifically
~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/
When Rhino first runs, it walks through each of the directories in MacPlugIns and tries to load every .rhp file. You’ll notice in the previous screenshot that there was another directory called “HelloMonoPlugIn”. This is a plug-in project that I wrote that was entirely built and debugged on Mac Rhino.
Writing your own .NET plug-in for Mac Rhino
Let’s focus on how to write your own “HelloMonoPlugIn” so you can start experimenting with plug-in development on the Mac. First, you are going to need to install some development tools
- Install XCode – this application comes with you Mac and is on your Mac DVD, but is not installed by default with OSX
- Install MonoDevelop (version 2.4.2 at the time of this writing) – http://monodevelop.com/
MonoDevelop is the tool that you will use to write and test .NET (mono) plug-ins on Mac Rhino. If you’ve used Visual Studio, you shouldn’t have much difficulty getting used to MonoDevelop.
There are no project wizards yet, so we’re going to need to do this the long way
Fire up MonoDevelop and select “Start a New Solution”
Create a new C# Library project named something like “MyMacPlugIn”. Don’t check and of the “Project Features”, you won’t need them. After clicking the OK button, you should have a simple C# library project ready for you to type on.
We need to add a reference to RhinoCommon.dll to our project. This dll is located inside of the Rhinoceros application bundle that is installed when you installed Mac Rhino. On the menu click Project and then “Edit References…”. Pick the “.NET Assembly” tab and find your way to the Rhinoceros.app inside of the applications directory. I know, the user interface for navigating to the assembly is a little weird in this dialog, but I’ve got faith in you:) You should be able to find RhinoCommon at
/Applications/Rhinoceros.app/Contents/Resources/RhinoCommon.dll
Double click on RhinoCommon.dll and it will be added to your project’s list of references
Just got back from a trip to New York and Boston for the ACADIA conference (http://www.acadia.org/acadia2010/). I want to thank everyone that spent the time to meet with me and talk python. In particular, it was great getting together with
Marc Fornes – http://theverymany.com/
Skylar Tibbits – http://www.sjet.us/
who from what I can tell are doing most of their Rhino oriented scripting in python now. Nice!
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:
- 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
- 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
- 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)
- 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.
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 |
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==(count–1)): | |
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() |