31 Oct 2017

sample scripts on basic workflow of translating model in Python

Default blog image

When I investigated one case of translating model to SVF (format for Forge Viewer), I found an old script of Python that we have not migrated. So I took time to make it working with latest version of Forge services. It shows the whole workflow from getting token, creating bucket, uploading object to requesting translating the object. I think it would be useful to some programmers of Python, so forked to my Github:

https://github.com/xiaodongliang/forge.workflow-python-sample

The code is enclosed here to be more searchable on internet.

#!/usr/bin/env python
# pyadva.py - demonstrate Autodesk View and Data API authorisation and translation process in Python
#
# Copyright (C) 2017 by Forge Partnership, Autodesk Inc.
#
import base64, json, md5, os.path, requests, shutil, time
from optparse import OptionParser

_version = '2.0'

BASE_URL = 'https://developer.api.autodesk.com/'
BUCKET_KEY = '<your bucket name>'

_file_missing_prompt = "Error: specified %s file '%s' does not exist.\n"


def parse_credentials(filename):
  "Parse credentials from given text file."
  f = open( filename )
  lines = f.readlines()
  f.close()
  credentials = []
  for line in lines:
    i = line.find('#')
    if -1 < i: line = line[0:i]
    i = line.find(':')
    if -1 < i: line = line[i+1:]
    line = line.strip()
    if 0 < len(line):
      print line
      line = line.strip("\"'")
      credentials.append(line)

  if 2 != len(credentials):
    raise "Invalid credentials: expected two entries, consumer key and secret;\nread %s lines, %s after stripping comments." % (len(lines),len(credentials))
    credentials = null

  return credentials


def main():
  "Drive Autodesk 3D viewer authorisation and translation process."
  global BUCKET_KEY
  
  progname = 'pylmv'
  usage = 'usage: %s [options]' % progname
  parser = OptionParser( usage, version = progname + ' ' + _version )
  parser.add_option( '-b', '--bucketskip', action='store_true', dest='bucketskip', help = 'skip bucket creation' )
  parser.add_option( '-c', '--credentials', dest='credentials_filename', help = 'credentials filename', metavar="FILE", default='credentials.txt' )
  parser.add_option( '-m', '--model', dest='model_filename', help = 'model filename', metavar="FILE", default='samples/mytestmodel.rvt' )
  parser.add_option( '-q', '--quiet', dest='quiet', action='store_true', default=False, help = 'reduce verbosity' )
  parser.add_option( '-u', '--urn', dest='urn', help = 'specify urn of already uploaded model file', default='' )

  (options, args) = parser.parse_args()

  print options
  print args

  verbose = not options.quiet

  if 1 < len( args ):
    raise SystemExit(parser.print_help() or 1)

  model_filepath = options.model_filename

  if not model_filepath:
    print 'Please specify a model file to process.'
    raise SystemExit(parser.print_help() or 2)

  if not os.path.exists( model_filepath ):
    print _file_missing_prompt % ('model', model_filepath)
    raise SystemExit(parser.print_help() or 3)

  # Step 1: Register and create application, retrieve credentials

  if not os.path.exists( options.credentials_filename ):
    print _file_missing_prompt % ('credentials', options.credentials_filename)
    raise SystemExit(parser.print_help() or 4)

  credentials = parse_credentials(options.credentials_filename)

  if not credentials:
    print "Invalid credentials specified in '%s'." % options.credentials_filename
    raise SystemExit(parser.print_help() or 5)

  consumer_key = credentials[0]
  consumer_secret = credentials[1]
  BUCKET_KEY =(BUCKET_KEY + '-' + consumer_key).lower ()
  
  # Step 2: Get your access token

  # curl -k \
  # --data "client_id=<your client id>&client_secret=<your client secret>&grant_type=client_credentials&scope=data:read data:write bucket:create bucket:read" \
  # https://developer.api.autodesk.com/authentication/v1/authenticate \
  # --header "Content-Type: application/x-www-form-urlencoded"

  url = BASE_URL + 'authentication/v1/authenticate'

  data = {
    'client_id' : consumer_key,
    'client_secret' : consumer_secret,
    'grant_type' : 'client_credentials',
    'scope':  'data:read data:write bucket:create bucket:read'
    
  }

  headers = {
    'Content-Type' : 'application/x-www-form-urlencoded'
  }

  r = requests.post(url, data=data, headers=headers)

  content = eval(r.content)

  if verbose or 200 != r.status_code:
    print r.status_code
    print r.headers['content-type']
    print type(r.content)
    print content
    # -- example results --
    # 200
    # application/json
    # {"token_type":"Bearer","expires_in":1799,"access_token":"ESzsFt7OZ90tSUBGh6JrPoBjpdEp"}

  if 200 != r.status_code:
    print "Authentication returned status code %s." % r.status_code
    raise SystemExit(6)

  access_token = content['access_token']

  print 'Step 2 returns access token', access_token

  # Step 3: Create a bucket

  if not options.bucketskip:

    # Check for prior existence:

    # curl -k -X GET \
    # -H "Authorization: Bearer lEaixuJ5wXby7Trk6Tb77g6Mi8IL" \
    # https://developer.api.autodesk.com/oss/v2/buckets/<your bucket name>/details

    url = BASE_URL + 'oss/v2/buckets/' + BUCKET_KEY + '/details'

    headers = {
      'Authorization' : 'Bearer ' + access_token
    }

    print 'Step 3: Check whether bucket exists'
    print 'curl -k -X GET -H "Authorization: Bearer %s" %s' % (access_token, url)

    print url
    print headers

    r = requests.get(url, headers=headers)

    if verbose:
      print r.status_code
      print r.headers['content-type']
      print r.content
      # -- example results --
      # 200
      # application/json; charset=utf-8
      # {
      #  "key":"jtbucket",
      #  "owner":"NjEasFuPL6WAsNctq3VCgXDnTUBGa858",
      #  "createDate":1404399358062,
      #  "permissions":[{"serviceId":"NjEasFuPL6WAsNctq3VCgXDnTUBGa858","access":"full"}],
      #  "policyKey":"transient"
      # }

    if 200 != r.status_code:

      # Create a new bucket:

      # curl -k \
      # --header "Content-Type: application/json" --header "Authorization: Bearer fDqpZKYM7ExcC2694eQ1pwe8nwnW" \
      # --data '{\"bucketKey\":\"<your bucket name>\",\"policyKey\":\"transient\"}' \
      # https://developer.api.autodesk.com/oss/v2/buckets

      url = BASE_URL + 'oss/v2/buckets'

      data = {
        'bucketKey' : BUCKET_KEY,
        'policyKey' : 'transient'
      }

      headers = {
        'Content-Type' : 'application/json',
        'Authorization' : 'Bearer ' + access_token
      }

      print 'Step 3: Create a bucket'
      print 'curl -k -H "Authorization: Bearer %s" -H "Content-Type:application/json" --data "{\\"bucketKey\\":\\"%s\\",\\"policyKey\\":\\"transient\\"}" %s' % (access_token, BUCKET_KEY, url)

      print url
      print json.dumps(data)
      print headers

      r = requests.post(url, data=json.dumps(data), headers=headers)

      if verbose or 200 != r.status_code:
        print r.status_code
        print r.headers['content-type']
        print r.content
        # -- example results --
        # The Python request call failed, but the curl
        # command that it generated worked and produced
        # the following result:
        #
        # {
        #  "key":"jtbucket",
        #  "owner":"NjEasFuPL6WAsNctq3VCgXDnTUBGa858",
        #  "createDate":1404399358062,
        #  "permissions":[{"serviceId":"NjEasFuPL6WAsNctq3VCgXDnTUBGa858","access":"full"}],
        #  "policyKey":"transient"
        # }

      if 200 != r.status_code:
        print "Bucket creation returned status code %s." % r.status_code
        raise SystemExit(7)

  # Step 4: Upload a file

  if options.urn:
    urn = options.urn
  else:
    # curl -k \
    # --header "Authorization: Bearer K16B98iaYNElzVheldlUAUqOoMRC" \
    # -H "Content-Type:application/octet-stream" \
    # --upload-file "<your object name>"
    # -X PUT https://developer.api.autodesk.com/oss/v2/buckets/<your bucket name>/objects/<your object name>

    filesize = os.path.getsize( model_filepath )
    model_filename = os.path.basename( model_filepath ).replace(' ', '+')
    #model_filename = model_filename.replace('.','_')

    url = 'https://developer.api.autodesk.com/oss/v2/buckets/' + BUCKET_KEY + '/objects/' + model_filename

    headers = {
      'Content-Type' : 'application/octet-stream',
      #'Content-Length' : str(filesize),
      'Authorization' : 'Bearer ' + access_token,
      #'Expect' : ''
    }

    print "Step 4: starting upload of model file '%s', %s bytes..." % (model_filename,filesize)

    print 'curl -k -H "Authorization: Bearer %s" -H "Content-Type:application/octet-stream" -T "%s" -X PUT %s' % (access_token, model_filepath, url)

    with open(model_filepath, 'rb') as f:
      #files = { model_filename : f }
      #r = requests.put(url, headers=headers, files=files)
      #uploading does not aceept multi-parts objects. 
      #see http://docs.python-requests.org/en/latest/api/
      # files: Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
      # data:  Dictionary, bytes, or file-like object to send in the body of the Request.
      r = requests.put(url, headers=headers, data=f) 

    #with open(model_filepath, 'rb') as f:
    #  request = requests.put(url, headers=headers, data=f)

    if verbose:
      print r.status_code
      print r.headers['content-type']
      print r.content
      # -- example results --
      # The Python request call failed, but the curl
      # command that it generated worked and produced
      # the following result:
      #
      # {
      #   "bucket-key" : "<your bucket name>",
      #   "objects" : [ {
      #     "location" : "https://developer.api.autodesk.com/oss/v1/buckets/jtbucket/objects/two_columns_rvt",
      #     "size" : 4165632,
      #     "key" : "two_columns_rvt",
      #     "id" : "urn:adsk.objects:os.object:jtbucket/two_columns_rvt",
      #     "sha-1" : "cb15374248562743c5a99e0bdb0535f508a19848",
      #     "content-type" : "application/octet-stream"
      #   } ]
      # }
    content = eval(r.content)
    #urn = content['objects'][0]['id']
    urn = content['objectId']
    print 'id:', urn

    # import base64
    # base64.b64encode("urn:adsk.objects:os.object:<your bucket name>/your object name")
    # 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6anRidWNrZXQvdHdvX2NvbHVtbnNfcnZ0'

    urn = base64.b64encode(urn)
    print 'urn:', urn

  # Step 6: Register Data with the Viewing Services

  # curl -k \
  # -H "Content-Type: application/json" \
  # -H "Authorization:Bearer 1f4bEhzvxJ9CMvMPSHD4gXO4SYEr" \
  # -i -d "{\"urn\":\"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bXlidWNrZXQvc2t5c2NwcjEuM2Rz\"}" \
  # https://developer.api.autodesk.com/modelderivative/v2/designdata/job

  url = BASE_URL + 'modelderivative/v2/designdata/job'

  data = {
    "input": {
        "urn": urn
    },
    "output": {
        "destination": {
            "region": "us"
        },
        "formats": [
        {
            "type": "svf",
            "views":["2d", "3d"]
        }]
    }
  }

  headers = {
    'Content-Type' : 'application/json',
    'Authorization' : 'Bearer ' + access_token
  }

  print 'Step 6: Request to translate the object'
  print 'curl -k -H "Authorization: Bearer %s" -H "Content-Type:application/json" -i -d "{\\"urn\\":\\"%s\\"}" %s' % (access_token, urn, url)

  print url
  print json.dumps(data)
  print headers

  r = requests.post(url, data=json.dumps(data), headers=headers)

  if verbose or 200 != r.status_code:
    print r.status_code
    print r.headers['content-type']
    print r.content
    # -- example results --
    # The Python request call failed, but the curl
    # command that it generated worked and produced
    # the following result:
    #

  if 200 != r.status_code:
    print "Register data returned status code %s." % r.status_code
    raise SystemExit(9)


if __name__ == '__main__':
  main()

The original author of this sample is my colleague Jeremy Tammik. Recently he also produced another blog on Python scripts. 

https://forge.autodesk.com/cloud_and_mobile/2016/12/forge-formats-python-scripts.html

 

Related Article