Welcome back. This is the continuation of our first blog posting in the Out of the Box series. These postings are designed to introduce you to and help you learn our new scripting API. We will be working in the Python language and will focus on the"in application" development environment (i.e., inside a running instance of Geomagc Studio 2012 or one of the other new products in our industrial lineup.)

In upcoming posts I will be covering the use of more sophisticated IDE's and external editors (like Eclipse/Pydev, WordPad++, etc.), but feel free to use them now for editing if you like. Or if you are so inclined, we have a brief section in Help that discusses using them for remote debugging (again, another topic for the very near future.)

Our first exercise is to to make a cube and then remove a sphere from its volume. There are many ways to create a box or cube. I have chosen to start with a feature plane, convert it to a polygon mesh, and then extrude the boundary and close the end. Mike Facello, our Director of R&D, caught me in the hall after reviewing my code (yes, we even code review our blog scripts) and he suggested that I encapsulate the box code in a function. Hey! Our first python tool! So that's what we will do in the last post of this series (i.e., create tools from all these steps.) OK let's start building our script from scratch.

I will assume you have installed, licensed, and started Geomagic Studio 2012 (or one of the other new 2012 industrial products) and the Scripting panel is open. You can switch to the Scripting tab by selecting it (it's behind the Graphics and Getting Started tabs.)

OutOfTheBox_Image_003.PNG

Next create a new Python script with File->New on the Scripting tab's File menu.

OutOfTheBox_Image_004.PNG

I usually start with existing scripts that can be harvested and/or repurposed, but since this is a from-scratch effort, let's start by opening the Scripting/API Help in your favorite internet browser. The API Help system is located off of your Geomagic installation folder:

<Geomagic_Install_Directory>\help\platform\index.html

OutOfTheBox_Image_005.PNG

I still find myself using the help examples on a daily basis, just due to the fact that they are a cut and paste operation. They are perfect for figuring out how objects, lists, commands, and modifiers all work together. I will usually create another script (name it something meaningful like addModel.py), cut and paste the code from the help page, and then make changes and run it (make changes, run it, etc.) just as an experiment to see what does what. These example snippets actually double as our unit tests that way we know that what we expose in the API is 1) documented, and 2) tested on a daily basis.

Which brings up a good point to make here be careful when pasting scripts from other editors, web pages, etc. Python is whitespace sensitive and tabs and spaces are treated differently by the parser. Sometimes errors can creep in just due to improper indentation. This also sounds like a good place to tell you about a few resources online that I used to research and improve my skills with Python.

This one is a great start for the uninitiated (non-programmers.) This one has a great deal of useful information:

wiki.python.org BeginnersGuide

Here's one that is more comprehensive. It was produced with great effort by Guido van Rossum (edited by Frank Drake):

python.org Tutorial

And for the non-readers in the crowd, here's a good video covering the Python language and interactive console. You will need to load Python to follow along with this (we suggest using Python version 2.7 for now):

Google YouTube Video

One thing to note is that a great deal of information about Python exists. However, after installing Geomagic's products, you don't need to install Python to begin scripting in Python within our applications. We install our own parser and everything you need to start coding with our API. So please, don't feel overwhelmed by all the content on the net. Install Python if you like, it will not interfere with our parser, and have fun. On the other hand, if learning the API is your highest priority, you should stick close to our API documents, our blog postings, and our soon to be published Knowledge Base Articles and forum. In the future, we will be increasing the difficulty and leveraging the true power of Python and Geomagic's application programming interface.

So to continue since we are starting with a plane, let's just simply begin by searching for plane using the Quick search text box in our API Help (it's in the far left column.)

Help comes back with quite the list of plane references. I don't expect you to know what all of them do yet so this being our first example, I'll just give you a hint select geoapi.features.PlaneFeature.

This command allows you to create a Feature Plane by specifying the plane's origin, normal, and U/V extents (these are important when controlling the size and graphical"extents" of the resulting geometry true in our case). To clarify, the origin is the mathematical point through which the infinite plane (defined by the normal vector) passes. The center is the Read Only origin at which the U and V extent values are zero.

So let's start scripting

Cutting and pasting the script example from the bottom of the Help page to the Scripting panel gives us:

OutOfTheBox_Image_006.PNG

Typically I will, before pasting, select all the contents of the new script (Ctrl-A and then Ctrl-V) to automatically replace the startup lines we generate by default. More precisely to replace these:

import geoappall
for m in geoappall.execStrings: exec m in globals(), locals()

These default lines import all of our geoapp modules in an effort to make it easier to start scripting. We also include these (and other variations) for each script example in the Help to ensure they run when copied, such as our geoapi modules for the PlaneFeature example.

Currently the overhead with doing this is low. However, as your skills increase and your needs change, you can selectively import only those modules and individual commands you need in order to free up memory and decrease load time. We will cover this in greater depth in an upcoming blog series.

Next, run the script. Clicking the Run button (circled below) doesn't seem to do anything but it did, we just didn't tell Python (or the application) to do anything with our plane. We didn't even ask it to tell us if the script was successful.

OutOfTheBox_Image_007.PNG

If the script had failed, Python would have reported back with the usual stack of debug information. This will seem a little daunting at first, but the general rule of thumb is that the last line of the dump will have the most important information. Let's try"mistyping" the command name and see what we get back from the parser:

OutOfTheBox_Image_008.PNG

In the Output Window Python tells us that PlaneXFeature does not exist and that the error can be found on line 8. Other types of syntax errors will be more difficult to decipher, like leaving off a left or right parentheses, etc.

I'm still not sure if we actually did anything. Adding this line to the end of the script and rerunning will print out the text version of the plane object (SWIG format) and let you know if the plane1 object was created correctly.

print u"plane1:", str(plane1)

In the Output Window (note that the hexadecimal value will change) you should see the SWIG (Simplified Wrapper and Interface Generator) representation of the feature plane:

plane1: geoapi.features.PlaneFeature; proxy of Swig Object of type
'features::PlaneFeature *' at 0x0000000022405330

The print statement we added tells us that the PlaneFeature was created successfully, but we still can't"see" it. In order to make it visible to you we need to add it to the Model Manager. This is an important concept. One of the strengths of scripting is the ability to work"behind the scenes" without the graphics overhead. In other words, process the data and build what you need, and when ready, add the resultant model to the Model Manager tree (or add the Feature to an existing model) for presentation.

Since this is a Feature (not a polygon, point, or ordered point model) and we do not have an existing model to add it to, we can just proceed and convert our plane to a polygon mesh and add that mesh to the Model Manager.

Typing"convert" in the API Help search box gives a diverse range of topics. They all, in one form or another, convert one type of object, topology, or unit to another. We want to select CreateMeshFrom3DFeature. Alphabetically it should be the third hit in the search.

Selecting, copying, and pasting the following will give us a quick starting point to convert our plane to a polygon mesh.

# Create and run modifier to convert cylinder to a mesh.
createMeshFrom3DFeature = CreateMeshFrom3DFeature()
createMeshFrom3DFeature.feature = cylinder
createMeshFrom3DFeature.run()
# Mesh that approximates the surface of the cylinder.
mesh = createMeshFrom3DFeature.mesh

Replacing cylinder (from the Help example) with plane1 and adding a print statement to let us know the conversion was a success is all that is needed to repurpose this portion of code

# Create and run a modifier to convert our plane to a mesh
createMeshFrom3DFeature = CreateMeshFrom3DFeature()
createMeshFrom3DFeature.feature = plane1
createMeshFrom3DFeature.run()
# Mesh that approximates the surface of the plane.
planeMesh = createMeshFrom3DFeature.mesh
print u"planeMesh:", str(planeMesh)

But before we add it to the Model Manager, let's extrude the boundary to give our plane depth and use the option to close the end at the same time. Select and copy the following code from the ExtrudeHeight Help page to the bottom of our script:

# create an edge selection
esel = EdgeSelection(m)
esel.markBoundary(True)
bdef = OrientedTriangle()
# extrude the boundary of the hole in the positive x direction
extrude = ExtrudeHeight(m)
extrude.height = 0.1
extrude.doIntersection = True
extrude.direction = Vector3D(1, 0, 0)
extrude.closeExtrusion = False
extrude.run(esel.first())

Then edit the code (shown below) to create an Edge Selection for our plane and mark the boundary. After that, pass this edge selection as a parameter to our planeMesh ExtrudeHeight modifier, setting the .closeExtrusion parameter to True, removing the doIntersection parameter (we're not intersecting anything), and finally updating the direction and height. This should give us a cube behind the scenes.

# create an edge selection
esel = EdgeSelection(planeMesh)
esel.markBoundary(True)
# extrude the boundary of our plane in the positive x direction
extrude = ExtrudeHeight(planeMesh)
extrude.height = sideLength
extrude.direction = Vector3D(0, 0, -1)
extrude.closeExtrusion = True
extrude.run(esel.first())

If you noticed the sideLength variable, I created it (and sphereDiameter) at the top of my script to simplify the coding. Using sideLength (hint: could be a function parameter) I centered my cube over the origin (hint: centered; a function switch?) using the FeaturePlane's origin, uDir, uRange, and vRange parameters. Here is the whole script so far:

import geoapiall
for m in geoapiall.modules: exec"from %s import *" % m in locals(), globals()
import geoappall
for m in geoappall.execStrings: exec m in globals(), locals()
sideLength = 1.0
sphereDiameter = 1.38
# Create plane
plane1 = PlaneFeature()
plane1.normal = Vector3D(0.0, 0.0, 1.0)
plane1.origin = Vector3D(0.0, 0.0, sideLength/2)
plane1.uDir = Vector3D(1.0, 0.0, 0.0)
plane1.uRange = Vector2D(-sideLength/2,sideLength/2)
plane1.vRange = Vector2D(-sideLength/2,sideLength/2)
print u"plane1:", str(plane1)
# Create and run a modifier to convert our plane to a mesh
createMeshFrom3DFeature = CreateMeshFrom3DFeature()
createMeshFrom3DFeature.feature = plane1
createMeshFrom3DFeature.run()
# Mesh that approximates the surface of the plane.
planeMesh = createMeshFrom3DFeature.mesh
print u"planeMesh:", str(planeMesh)
# create an edge selection.
esel = EdgeSelection(planeMesh)
esel.markBoundary(True)
# extrude the boundary of our plane in the negative z direction
extrude = ExtrudeHeight(planeMesh)
extrude.height = 1.0
extrude.direction = Vector3D(0, 0, -1)
extrude.closeExtrusion = True
extrude.run(esel.first()) # esel.first()

Next, without further ado, we add it to the Model Manager.

Going back to API Help, type in"add Model". The first hit is addModel. In the example script for this command there are two areas on which to focus. The first being the modules we need to include when working with models and the Model Manager. These should be inserted at the top of your script they are the lines we pasted over when we started, and yes, you could have left them there:

import geoappall
for m in geoappall.execStrings: exec m in globals(), locals()

We already have added the modules needed to create a feature and convert it to a polygon mesh (geoapi), but not to add this mesh to the Model Manager (geoapp). With that done, the next area we need look at is the actual .addModel command.

These next lines need to be appended to the end of the script. Be sure to change pts' to our polygon mesh (planeMesh) and give it meaningful name like"Cube". The redraw command forces the Graphics tab to update after your changes.

# Add the extruded plane mesh to the model mgr
geoapp.addModel(planeMesh, u"OutOfTheBox")
geoapp.redraw(True)

Running the script should create a"Cube" model in your Model Manager tree. Switching to the Graphics tab you should finally"see" your work.

OutOfTheBox_Image_009.PNG

You'll notice there is a red selection that is active on the model. The ExtrudeHeight leaves this by default and we can deactivate it by inserting the following lines into the script before adding the model to the Model Manager:

# Deselect all triangles after the extrude
geoapp.clearActiveTriangleSelection(planeMesh)

That's all today. The updated code can be copied and pasted from the end of the script, or downloaded from here. We'll tackle the sphere next time; show you how to record the creation of a 3D PDF, and how to paste that recorded macro' directly into your code for inline execution. I didn't mention that all recorded macros can be inserted and run inline? Well you can do that too!

Stay tuned!

Richard

Go to Part One Go to Part Three

Here's the script from today...

OutoftheBox_Part2.py

# --------------------------------------------------------------------------------
# Geomagic, Inc - Scripting API Sample Script
# Title: Scripting Out of the Box Part 2
# Script File: OutOfTheBox_Part2.py
# Author: Richard Sandham
# Date: 11/06/2011
#---------------------------------------------------------------------------------
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR GEOMAGIC, INC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
#
# --------------------------------------------------------------------------------
#
# Imports
#
import geoapiall
for m in geoapiall.modules: exec"from %s import *" % m in locals(), globals()
import geoappall
for m in geoappall.execStrings: exec m in globals(), locals()

sideLength = 1.0
sphereDiameter = 1.38

# Create plane
plane1 = PlaneFeature()
plane1.normal = Vector3D(0.0, 0.0, 1.0)
plane1.origin = Vector3D(0.0, 0.0, sideLength/2)
plane1.uDir = Vector3D(1.0, 0.0, 0.0)
plane1.uRange = Vector2D(-sideLength/2,sideLength/2)
plane1.vRange = Vector2D(-sideLength/2,sideLength/2)

print u"plane1:", str(plane1)

# Create and run a modifier to convert our plane to a mesh.
createMeshFrom3DFeature = CreateMeshFrom3DFeature()
createMeshFrom3DFeature.feature = plane1
createMeshFrom3DFeature.run()

# Mesh that approximates the surface of the plane.
planeMesh = createMeshFrom3DFeature.mesh

print u"planeMesh:", str(planeMesh)

# create an edge selection.
esel = EdgeSelection(planeMesh)
esel.markBoundary(True)

# extrude the boundary of our plane in the negative z direction
extrude = ExtrudeHeight(planeMesh)
extrude.height = 1.0
extrude.direction = Vector3D(0, 0, -1)
extrude.closeExtrusion = True
extrude.run(esel.first()) # esel.first()

# Deselect all triangles after the extrude
geoapp.clearActiveTriangleSelection(planeMesh)

# Add the plane to the model mgr
geoapp.addModel(planeMesh, u"OutOfTheBox")