Glyph API for Python

glf_file_256_python.png

Ever wish you could write Glyph scripts to extend or customize the functionality of Pointwise in some language other than Tcl/Tk? Python coders: wish no more! In conjunction with the release of Pointwise V18.2 in September 2018, we’ve developed and published a wrapper API that provides full access to Glyph commands in a very Python-ic way that is still very familiar to the Tcl/Tk script writer. To a Python coder, the interface looks and feels like native Python, with the clear advantage of being able to use the full power of any community-developed Python package.

(Before we go further, it’s important to note that, in this article, Glyph refers the Tcl-based command set that is published by Pointwise. Whenever we refer to Glyph, we really mean the Glyph API for Tcl/Tk. We’ll refer to the Python API as Python/Glyph.)

BouffonPython.png

A sample Glyph/Python script using Tkinter and matplotlib.

What is Glyph API?

Starting with Pointwise V18.0 R2 (November 2016), the native Glyph interface was enhanced to enable access by virtually any scripting language (or compiled language for that matter) that supports TCP/IP socket communication. The key addition was a component called Glyph Server which allows an external process to connect to a user-specified port and interact with Pointwise to execute Glyph actions. The net result is that any program or script that can connect to the specified TCP port on a host running Pointwise (with the Glyph Server option enabled in the Script menu) can transmit a message containing a Glyph command and receive a message that includes the Tcl-formatted return value along with the Tcl result code.

At the same time V18.0 R2 was released with the Glyph Server capability, we also published the Python Glyph Client on the Glyph Script Exchange hosted on GitHub that implemented the TCP/IP connection and basic message processing for Python. This established the baseline mechanism for communicating with a running instance of Pointwise from a Python script, but it did not provide any way to represent Glyph objects and lists as Python objects and containers — that was left as an exercise for the reader. But that still fell short of something that looked and felt like real Python, and that’s where the Glyph API for Python comes in.

GCPArchitecture

Architecture of the Pointwise Glyph Server

The Glyph API for Python

With the release of V18.2 R2 in September 2018, we extended the Python client package to include the Glyph API for Python which makes Glyph scripting in Python look and act like, well… Python. That is, rather than require your Python script to build up a Glyph command string and send it to the server, and then process the results as shown below

from pointwise import GlyphClient

glf = GlyphClient(port=2807)
dim = 11
con1 = glf.eval("pw::Connector create")       # con1 = "::pw::Connector_1"
glf.eval(con1 + " setDimension " + str(dim))  #ugly

you can now write something like

from pointwise import GlyphClient
from pointwise.glyphapi import *

glf = GlyphClient(port=2807)
pw = glf.get_glyphapi()
dim = 11
con1 = pw.Connector()                      # con1 is a Python object!
con1.setDimension(dim)                     #realpython

Python/Glyph takes care of generating the required Glyph command string, sending the request, processing the Glyph result string and code, and creating Python wrapper objects. In fact, it does this in such a way that makes every Glyph command (past, present and future) available as a Python object that looks and acts (mostly) like you’d expect.

Example

The GitHub project contains some concrete example usages of the Python API, including a fully scripted version of the oh-so-familiar Backstep Tutorial (available in the tutorial workbook included with all releases of Pointwise). But, to whet your appetite a little, here’s a simple example that creates a square unstructured domain from a loop of points.

from pointwise import GlyphClient
from pointwise.glyphapi import *

# port 0 is special as it launches a Glyph batch process locally
glf = GlyphClient(port=0)
pw = glf.get_glyphapi()

pw.Connector.setCalculateDimensionMethod("Spacing")
pw.Connector.setCalculateDimensionSpacing(.3)

# create a loop of connectors from a cyclic list of points
points = [(0, 0, 0), (10, 0, 0), (10, 10, 0), (0, 10, 0)]
cons = []
with pw.Application.begin("Create") as creator:
    for p1, p2 in zip(points, points[1:]+points[:1]):
        seg = pw.SegmentSpline()
        seg.addPoint(p1)
        seg.addPoint(p2)
        con = pw.Connector()
        con.addSegment(seg)
        con.calculateDimension() 
        cons.append(con)
    creator.end()

# create an unstructured domain from the connectors
with pw.Application.begin("Create") as creator:
    edge = pw.Edge.createFromConnectors(cons)
    dom = pw.DomainUnstructured()
    dom.addEdge(edge)
    creator.end()

print(dom)

The Details: Mapping Python to Glyph

The full details of how to use Python/Glyph are on GitHub, but a few things are worth mentioning here. One of the primary objectives of this project was to minimize or eliminate the need to maintain Python code in concert with Pointwise and Glyph. That is, as Pointwise and Glyph evolve with new object types and actions, it would be extremely costly and time-consuming if we had to duplicate all the effort needed for development, testing and documentation of a full Python API in addition to the traditional Tcl API.

Fortunately, the Python language contains many features that allow us to automatically map Glyph object types and actions to Python wrapper objects with real attributes and methods. That is, by mapping Python classes, objects, attributes and methods automatically to Glyph, there is no need for individual classes, attributes and methods to be defined in the Python API. The result is that all Glyph objects and classes in Python become instances of a special wrapper class called GlyphObject, which takes care of the finer details of mapping the actual type, attributes and actions of their associated concrete Glyph objects in Pointwise. So, there’s not a Python class called Connector or DomainUnstructured, and thus no instances of those types, but there is an instance of GlyphObject of each that knows how to map those things to Glyph.

Glyph Mapping Rules

Some of the rules for mapping Python/Glyph to Glyph are implied in the examples above, but we’ll detail the basic ones here. Before Python/Glyph can be used, a connection to a Glyph Server must be first be established by instantiating a GlyphClient in the “pointwise” package. (We’ll refer to this object as “glf”.) Note that a connection specified as port “0” on the default “localhost” will cause Python/Glyph to launch a new batch Glyph (tclsh) process and start a Glyph Server on any available port, then terminate the process when the connection is closed.

  1. The top level of the Python/Glyph API is always the object returned by “glf.get_glyphapi()”. All subsequent Python/Glyph instances created using this object are ultimately linked to this top-level object and therefore also linked to the Pointwise workspace in which they exist. (We’ll refer to this object as “pw”.)
    Tcl/Tk:  package require PWI_Glyph 2    # establish the 'pw' namespace
    Python:  glf = GlyphClient(port=0)
             pw = glf.get_glyphapi()
  2. Glyph class names are case-sensitive for both Python and Tcl, so the names must match exactly.
  3. All Glyph classes are effectively attributes of the top-level (“pw”) object.
    Tcl/Tk:  pw::Application
    Python:  pw.Application
  4. Any Glyph class that exposes a static “create” action can be instantiated directly using the syntax “pw.GlyphClassName()”. If the create action accepts arguments, they can be passed just like any other argument (see rule 8).
    Tcl/Tk:  set con [pw::Connector create]
    Python:  con = pw.Connector()
  5. All static Glyph class actions are invoked on the equivalent Python/Glyph object.
    Tcl/Tk:  pw::Application getVersion
    Python:  pw.Application.getVersion()
  6. Glyph actions that return Glyph objects are wrapped in an instance of GlyphObject which acts in a manner consistent with the associated Glyph object. Only instance actions may be invoked on this object, and there are no attributes that act implicitly as “setter/getter” methods (a common Python idiom).
  7. Instance methods on the Python/Glyph object are converted to Glyph instance actions.
    Tcl/Tk:  set dim [$con getDimension]
    Python   dim = con.getDimension()
  8. Command arguments can be either positional and/or keyword for both Python and Tcl, but the rules for mapping them are a bit more complicated than one might expect:
    • Python keyword arguments are used for all Glyph flag arguments. There is no way to specify Glyph flags other than as keyword arguments.
      Tcl/Tk:  $crv smoothC1 -tolerance $tol
      Python:  crv.smoothC1(tolerance=tol)
    • Glyph flags that do not accept arguments must be specified as a keyword with a value of True in Python.
      Tcl/Tk:  $con1 join -keepDistribution $con2
      Python:  con1.join(con2, keepDistribution=True)
    • Positional arguments appear last for Tcl, but must appear first in the Python call list.
      Tcl/Tk:  $con fitLSQ -tolerance $tol $refCon
      Python:  con.fitLSQ(refCon, tolerance=tol)
    • Note that some keyword parameters may appear to include a value, but a close look at the Glyph documentation will reveal that they do not. In these cases, using either form of the Python/Glyph command will work, but be aware that it’s a syntactic anomaly.
      Tcl/Tk: $con getPosition -arc 0.5
      Python: con.getPosition(0.5, arc=True)  # the intended form
              con.getPosition(arc=0.5)        # works, but not correct
    • Lists (or tuples) may be passed as arguments, and are converted to Tcl lists except under certain circumstances. (For these additional rules, see the documentation on the main GitHub page.)
      Tcl/Tk:  $segment addPoint {0 0 0}
      Python:  segment.addPoint((0, 0, 0))  # a single tuple argument

Advanced Python/Glyph Lists

The Python language provides some very unique syntactic features for dealing with containers of objects and values. Experienced Python coders will find that Python/Glyph list handling is not as elegant as they’d like it to be. Due to the automatic one-to-one mapping scheme we’ve adopted, Python/Glyph scripts will tend to mimic Glyph fairly closely. If you’re familiar with Glyph scripting in Tcl/Tk, you’ll remember that most of the actions that deal with collections or lists of information (e.g., points or cells) can only be retrieved one at a time using a loop of the form:

set elist [list]
for { set i 1 } { $i <= [$entity getObjectCount] } { incr i } {
    lappend elist [$entity getObject $i]
}

This was a conscious design choice in Pointwise given that many Glyph objects can potentially contain a very large number of sub-elements, and it tends to be very inefficient (memory-wise) to convert lists of elements to their Tcl (string) equivalent. For the example above, the equivalent Python code is usually something like:

elist = []
for i in range(1, entity.getObjectCount()+1):
    elist.append(entity.getObject(i))

Well… yuck. But, for potentially large collections of things (like points or cells or Examine metric data) consider how much extra overhead (memory) would be needed to convert the list from Glyph to Tcl (string form) and from Tcl to Python. (Remember that all Glyph objects are communicated as strings over the TCP/IP connection.) The tradeoff, of course, is the number of TCP/IP round trips that have to be made to retrieve a full list of objects in this manner.

As a compromise, we have near-term development plans to add many direct “getObjectList” actions to Glyph for object/value lists that are not typically large. For example, in Pointwise V18.2 R2, you will be able to retrieve all the control points or grid points of a connector:

con = pw.GridEntity.getByName("con-1")
segs = con.getSegments()
segpts = seg[0].getPoints()
conpts = con.getPoints()  # position of grid points on connector
gridxyzs = con.getXYZs(grid=True)   # XYZ values of all grid points
cpxyzs = con.getXYZs(control=True)  # XYZ values of all control points

This should satisfy most small-ish object lists, but the potentially large lists of things like points and cells will still have to be retrieved one at a time. Now, a savvy Python coder with some Tcl cred might recognize that full or partial sets of these large object lists could be retrieved directly through the client API using something like:

cmd = "set pts [list]\n"
cmd += "set dom [pw::GridEntity getByName " + dom.getName() + "]\n"
cmd += "for {set i 1} {$i < [$dom getPointCount] + 1} {incr i} {\n"
cmd += "  lappend pts [$dom getPoint $i]\n"
cmd += "}\n"
cmd += "set pts\n"

result = glf.eval(cmd)
# 'result' is now a raw string rep of a Tcl list {{x1 y1 z1} ...},
# but we can convert it to Python with some inside knowledge:
pts = dom._toPythonObj(result)
# pts is now a python list of lists of real numbers [[x1, y1, z1], ...]

Fair warning, this idiom should be used with care, noting that very large collections will transiently require a lot of memory for the intermediate string representations on both sides of the server connection. A more palatable implementation might be to retrieve the lists in chunks by repeatedly using a technique similar to the above.

Get Python/Glyph

The Python/Glyph package is already available on the Glyph Script Exchange hosted on GitHub, along with some examples to get you started. Note that there is one external package dependency, numpy, which was used to ease the implementation of some of the utility functions directly in Python. You can also install Python/Glyph and its dependencies using pip with a command like:

python -m pip install pointwise-glyph-client

If you’re a current customer, feel free to download and try it out. It works with either Python 2 or 3. All feedback is welcome, and you’re free to modify or extend the API to suit yourself. As with all of our GitHub projects, improvement through the community is always welcome!Download-200x72.png

If you’re not a current customer but want to begin automating your mesh generation process with macros and templates written in Python, now’s the time to request a Free Trial.FreeTrial-200x72.png

Acknowledgements

A significant portion of Python API for Glyph was implemented as a summer intern project by Tyler Anderson, an Applied Mathematics major at Texas A&M University, under the direction of Mike Jefferies, lead engineer (and Python maven) of the Pointwise product development team.

This entry was posted in Applications, Software and tagged , , , , . Bookmark the permalink.

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