Sculpture has always been a fascination for me and has influenced my career more of late than during the earlier years. Mixing art and technology has always proven interesting and adding ancient culture to the equation raises the bar to new levels.

The University of South Florida has been using the latest technology to study the most ancient relics found in the"new world" and I cannot seem to keep myself from gravitating to their work. Dr. Lori Collins, Dr. Travis Doering, and their devout team at USF's AIST travel to the ends of civilization, into the jungle...literally. Places from which I would feel lucky if I came out alive. What they bring back are not ancient relics or plaster casts...they bring back data, 3D data. They pack-in scanners and laptops, leaving the mules behind, and return with complete digital representations of incredible archaeological finds. More importantly they leave the monuments and original pieces of sculpture in-place, untouched.

In contrast to older methods (i.e., sketches and plaster casts) the 3D data allows them to study these items (as large as a house, or as small as a coin) in their virtual environments back at campus. They can change the lighting and enhance surface texture, compare and contrast icons found thousands of miles apart in the jungle with relative ease and in full 3D.

These methods enable them to extract barely visible detail, reveal data that has been previously missed due the the perspective of the original sketch, and even correct the"artistic freedom" taken by the"artist" originally in the field. Above all, the digital nature of the data allows them to share their work among their colleagues worldwide via the net and give"the controls" over to the students and professors in truly immersive and engaging ways.

One such artifact was given to us to play with (digitally of course) and they asked if it could be"unrolled." Below is an image of an ancient Mayan"seal" that was used as a"signature" or stamp by rolling the low level relief cylinder in soft lime mortar leaving behing the impression seen at the bottom of the image.

mayawrit3.jpg

We had been talking about such a digital"tool" using scripts and I had my ideas on how to do it...but it wasn't until I took a trip down to see them in Tampa that a spark of creativity hit me...and the simple fact that I had built a library of tools in Python that made it possible.

Sitting in the bar at the Tampa airport on the return leg I mashed together a script that I thought might work...and it did the first time! ...well it was a big deal for me anyway...and after picking my jaw up off the bar, I closed the laptop, boarded the plane, and slept the entire flight home.

Long story short...here is the artifact as scanned:

Seal_001.png

And here it is after unrolling:

Seal_unrolled.png

...and here is an image taken after adjusting the light source...(I apologize in advance for my sacrilegious slicing and rearranging, I just wanted to see the bird):

Seal_unrolled_light_001.png

The script can be found here.

So...what can YOU do with scripting and Geomagic, in a bar at the airport, in an hour?

Here is the script:

'''
cylindricalFlatten.py - University of South Florida, AIST

Low Relief"Un-Rolling"

Author : Richard Sandham
Date : 07/18/2012

-------------------------------------------------------------------------------------
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

------------------------------------------------------------------------------------

'''
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 locals(), globals()

import numpy as np
import math

#
# geometry helper functions
#
def angFromAxis( vVec, uVec = Vector3D( 1, 0, 0 ), aOrgVec = Vector3D( 0, 0, 0 ), normVec = Vector3D( 0, 0, 1 ) ):
'''
vVec - vector that the angle will be measured to (e.g., a point's location as a Vector3D)
uVec - vector that the angle will be measured from (defaults to 1,0,0 - World xAxis)
aOrgVec - local origin for angular measurement (defaults to 0,0,0 - World Origin)
normVec - plane on which the angle will be calculated (defaults to 0,0,1 - World zAxis)
'''
# translate all inputs from the local origin to world origin
uVec = uVec - aOrgVec
vVec = vVec - aOrgVec
normVec = normVec - aOrgVec

# project inputs to normal plane
uVec = uVec - uVec.along( normVec )
vVec = vVec - vVec.along( normVec )

# create yAxis
yAxis = Vector3D()
yAxis.cross( normVec, uVec )

# rotate the local coordinate system to the world
tempPts = Points()
tempPts.addPoint( vVec ) # index 1
tempPts.addPoint( uVec ) # index 2
tempPts.addPoint( yAxis ) # index 3
tempPts.addPoint( normVec ) # index 4

# calc rotation - uVec to World X Axis
rotUtoWorldX = Transform3D()
rotUtoWorldX.setRotationTransform( uVec, Vector3D( 1, 0, 0 ) )

tempPts.transform( rotUtoWorldX )

# extract new zAxis
zVec = tempPts.getPosition( 4 )

# calc rotation - normVec to the World Z Axis
rotNormtoZ = Transform3D()
rotNormtoZ.setRotationTransform( zVec, Vector3D( 0, 0, 1 ) )

tempPts.transform( rotNormtoZ )

# extract new vectors
pVec = tempPts.getPosition( 1 )
xVec = tempPts.getPosition( 2 )
yVec = tempPts.getPosition( 3 )
zVec = tempPts.getPosition( 4 )


# set up the numpy arraya
u = np.array( [ xVec.x(), xVec.y(), xVec.z() ] )
v = np.array( [ pVec.x(), pVec.y(), pVec.z() ] )

# calc the dot
dot = np.dot( u, v )

# calc the angle
angle = np.arccos( dot / np.sqrt( ( u * u ).sum() ) / np.sqrt( ( v * v ).sum() ) )

# quadrants
if pVec.y() >= 0:
pass
else:
angle = ( math.pi * 2 ) - angle


return( float( math.fabs( angle ) ) )

def distFromAxis( pVec, aOrg = Vector3D( 0, 0, 0 ), aVec = Vector3D( 0, 0, 1 ) ):
'''
calculate the minimum (orthogonal) distance from the given axis (given in point/vector form: aOrg, aVec)
to the given point pVec
'''
p2 = pVec - aOrg
return( Vector3D( p2.normalTo( aVec ) ).length() )


activeModel = geoapp.getActiveModel()

scanPts = geoapp.getPoints( activeModel )

scanMesh = geoapp.getMesh( activeModel )

if scanPts != None:

# calc the distance and angle of each point from the x-axis
ptsSel = PointSelection( scanPts )
ptsSel.selectAll()

distMean = 0
indx = 0

for pt in ptsSel:
# calc distance from axis of rotation (defaultS to positive z-axis, 0,0,1)
ptDist = distFromAxis( scanPts.getPosition( pt ) )
indx = indx + 1
distMean = distMean + ptDist

distMean = distMean / indx

for pt in ptsSel:
zPos = ( scanPts.getPosition( pt ) ).z()
ptDist = distFromAxis( scanPts.getPosition( pt ) )
scanPts.setPosition( pt, Vector3D( ptDist, distMean * angFromAxis( scanPts.getPosition( pt ) ) , zPos ) )

else:

print"No Scan Data was found!"