Sample Python Script

Overview

The goal of this sample python script is to demonstrate how to use the Machine Analytics API to create a capture and upload a log file. The flow of the script is as follows:

  1. Authenticate user to gain access to APIs.
  2. Obtain a modeler type and capture type.
  3. Create a modeler of type obtained above
  4. Create a capture and upload log file to backend
  5. List your builds
  6. Do some cleanup

Global variables

Base url where our data endpoints live. The {} is a place holder

DATA_URL = 'https://data.authentise.com/{}/'

URL for authentication

USERS_URL = 'https://users.authentise.com/sessions/'

Path pointing to file to be uploaded

CAPTURE_PATH = 'modeler_status_1.log'

HTTP requests headers

HEADERS = {
   'Content-Type' : 'application/json',
   'Accept'       : 'application/json',
}

Authentise username and password for authentication

user_data = {
   'username' : 'engineering@authentise.com',
   'password' : 'password',
}

The attributes of the modeler

MODELER_DATA = {
    'ip_address'    : '10.0.0.10',
    'name'          : 'My printer name',
    'description'   : 'Blue Steel',
    'serial_number' : 'ABBA',
}

Function calls

  1. Authenticate

    _make_request('POST', USER_URL, user_data)

    Send the request to the endpoint so we can authenticate the session and be granted permissions.

  2. List modeler types to create a modeler

    modeler_type, capture_type = _get_capture_type_status_uri(_list_modeler_types())

    Get a modeler type and its associated capture_type.

  3. Create modeler

    modeler = _create_modeler(modeler_type)

    Create modeler and assigns it to the modeler variable.

  4. Create a capture and upload log file.

    _create_capture(modeler, capture_type)

    Use the modeler and capture_type we got above to create a capture and upload the file to the backend.

  5. List your builds.

    builds = _list_builds()

    Call the build endpoint so we can see the progress of our build based on the capture upload.

  6. Clean up.

    _delete_modeler(modeler['location'])

    Because this is a sample script I used a fake modeler and didn’t want to clutter up the database.

Considerations

This script is a sample of how to call the endpoints to create a capture in Python. It is intended as an example for you to build a script that would be suited to your environment.

We’ll be using the python requests library. It’s a 3rd-party library, but the API should be clear enough to understand well how it works without being familiar with the library specifics

Sample code

Python sample script

#!/usr/bin/env python3
import gzip
import pprint
import requests

"""change these global variables for your environment"""

DATA_URL         = 'https://data.authentise.com/{}/'
USERS_URL        = 'https://users.authentise.com/sessions/'
CAPTURE_PATH     = 'modeler_status_1.log'

HEADERS = {
    'Content-Type' : 'application/json',
    'Accept'       : 'application/json',
}
USER_DATA = {
    'username' : 'test@authentise.com',
    'password' : '*******',
}

session = requests.Session()
def _make_request(method, url, payload=None, headers=HEADERS):
    response = session.request(method, url, json=payload, headers=HEADERS)
    if not response.ok:
        raise Exception("Bad response from {}: {}".format(url, response.json()))
    if method == 'POST':
        return response.headers
    elif method == 'PUT':
        return None
    elif method == 'DELETE':
        return None
    else:
        return response.json()

The sample code above forms the basis for working with the variou APIs of 3DIAX. All requests we make will include the correct Content-Type and Accept header to tell the API we are sending JSON and expect to receive JSON.

The first thing we’ll need to do in our script is to decide which kind of modeler we are going to imitate. To do that we will list the different capture types.

# ...snip...
def _list_capture_types():
    return _make_request('GET', DATA_URL.format('capture-type'))


def main():
    # authenticate
    _make_request('POST', USERS_URL, USER_DATA)

    # show the different capture types
    pprint.pprint(_list_capture_types())
    return

if __name__ == '__main__':
    main()

If we run the above script as-is we’ll get back a list of capture-type resources. It looks something like this

{
  "resources": [
    {
      "capture_type": "catex.modeler.status",
      "interval": 300,
      "uri": "https://data.authentise.com/capture-type/f9fa10dc-3bdb-463f-a6a6-7e6934aeae97/",
      "modeler_type": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/"
    },
    {
      "capture_type": "catex.modeler.jobs",
      "interval": null,
      "uri": "https://data.authentise.com/capture-type/dfa7a7b6-fc30-453e-b516-76b0a9bfe768/",
      "modeler_type": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/"
    },
    {
      "capture_type": "connex.modeler.log",
      "interval": 300,
      "uri": "https://data.authentise.com/capture-type/e428be7e-c42c-4b16-8972-c819ba5d4611/",
      "modeler_type": "https://data.authentise.com/modeler-type/85be369e-83a2-4f39-ba7c-90887abc3898/"
    }
  ]
}

For this tutorial we will use catex.modeler.status - that’s the log file that certain Stratasys FDM machines create at regular intervals during normal operation that tell us the material present, the job they are printing, temperature, etc.

In order to actually send up this log information we need to create a modeler resource. This represents a printer in the system. In order to create a modeler we need to know what types the system supports

# ...snip...
def _list_modeler_types():
    return _make_request('GET', DATA_URL.format('modeler-type'))


def main():
    # authenticate
    _make_request('POST', USERS_URL, USER_DATA)

    # show the different capture types
    pprint.pprint(_list_modeler_types())
    return

if __name__ == '__main__':
    main()

When we run the code above we get the data on the modeler types in the system. It looks something like this

{
  "resources": [
    {
      "description": null,
      "name": "Fortus 250",
      "uri": "https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/",
      "technology": "FDM",
      "manufacturer": "Stratasys",
      "capture_types": [
        {
          "capture_type": "catex.modeler.jobs",
          "interval": null,
          "uri": "https://data.authentise.com/capture-type/bc53aced-b309-4bdf-8ece-69947f4a9db7/"
        },
        {
          "capture_type": "catex.modeler.status",
          "interval": 300,
          "uri": "https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/"
        }
      ]
    },
    {
      "description": null,
      "name": "Fortus 400",
      "uri": "https://data.authentise.com/modeler-type/1df33a4c-1b96-4098-a44a-5c98c92b018b/",
      "technology": "FDM",
      "manufacturer": "Stratasys",
      "capture_types": [
        {
          "capture_type": "catex.modeler.jobs",
          "interval": null,
          "uri": "https://data.authentise.com/capture-type/64251c64-3b2c-4265-a8e8-48599494a902/"
        },
        {
          "capture_type": "catex.modeler.status",
          "interval": 300,
          "uri": "https://data.authentise.com/capture-type/103691f1-8188-4aad-b43e-a29ef3b726d7/"
        }
      ]
    },
    {
      "description": null,
      "name": "Objet 350",
      "uri": "https://data.authentise.com/modeler-type/85be369e-83a2-4f39-ba7c-90887abc3898/",
      "technology": "Binder Jet",
      "manufacturer": "Stratasys",
      "capture_types": [
        {
          "capture_type": "connex.modeler.log",
          "interval": 300,
          "uri": "https://data.authentise.com/capture-type/e428be7e-c42c-4b16-8972-c819ba5d4611/"
        }
      ]
    },
    {
      "description": null,
      "name": "Uprint",
      "uri": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/",
      "technology": "FDM",
      "manufacturer": "Stratasys",
      "capture_types": [
        {
          "capture_type": "catex.modeler.jobs",
          "interval": null,
          "uri": "https://data.authentise.com/capture-type/dfa7a7b6-fc30-453e-b516-76b0a9bfe768/"
        },
        {
          "capture_type": "catex.modeler.status",
          "interval": 300,
          "uri": "https://data.authentise.com/capture-type/f9fa10dc-3bdb-463f-a6a6-7e6934aeae97/"
        }
      ]
    }
  ]
}

We can see here that the Fortus 250 supports the capture type we’re interested in. Each of these resources are referenced by URI. We’re going to use the modeler https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/ and the capture type https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/

Now we need to create a modeler and a capture for the modeler that uses those URIs

# ... snip ...
def _create_modeler(modeler_type_uri):
    MODELER_DATA['type'] = modeler_type_uri
    return _make_request('POST', DATA_URL.format('modeler'), MODELER_DATA)

def _delete_modeler(modeler):
    _make_request('DELETE', modeler)

def _list_builds():
    return _make_request('GET', DATA_URL.format('build'))

def _create_capture(modeler, capture_type):
    payload = {
        'modeler'     : modeler['location'],
        'capture_type': capture_type
    }
    headers = _make_request('POST', DATA_URL.format('capture'), payload)
    capture_upload_url = headers["x-upload-location"]
    capture_uri = headers["Location"]
    return capture_uri, capture_upload_url

def main():
    # authenticate
    _make_request('POST', USERS_URL, USER_DATA)

    modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/'
    capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/'

    # create the mode1ler
    modeler = _create_modeler(modeler_type)
    pprint.pprint('Created modeler uri:{}'.format(modeler['location']))

    # create a capture
    capture, upload_url = _create_capture(modeler, capture_type)

    print("Created capture {}".format(capture)

We have now created a modeler and created a capture for our modeler. Normally Authentise Echo will send a new capture for each modeler it listens to once every 5 minutes. This is how the system knows that the printer is healthy and keeps track of what the printer is doing. In our example script we have created the capture but we have not uploaded any data. Creating the capture merely creates a location where the data could be uploaded

The capture that gets uploaded has a format that is specific to each printer type. The catex.modeler.status capture type expects a file that looks like this

set machineStatus(general) {
        -modelerStatus "Building"
        -modelerExplanation ": "
        -startTime "1457952053"
        -elapsedBuildTime 12721
        -modCount 143
        -modelerType "vt2l"
        -currentModelingLayer 34
        -partCurrentTemp 355
        -supportCurrentTemp 241
        -envelopeCurrentTemp 100
        -partTip "T12"
        -supportTip "T12SR100"
        -partSetTemp 355
        -supportSetTemp 240
        -envelopeSetTemp 100
        -serverTime 1457964774
        -serverTimeZoneOffset 21600
        -serverTimeDSTValue 1
        -controllerVersion " 3.17.2516.0 "
        -compatibleCMBVersion 9.3
};
set machineStatus(cassette) {
        {
        -spool 0
        -name "NYL12"
        -status "ACTIVE"
        -remainingMatl 25.209132
        -mfgDate "05122015"
        -mfgLot "6621"
        -initialMatl 92.300000
        -serialNumber 278929307
        }
};

set machineStatus(currentJob) {
        -jobName "my-job"
        -jobid 123
        -owner "Eli Ribble"
        -submitTime "1457951706"
        -buildTime 12721
        -estimatedBuildTime 27474
        -startTime "1457952053"
        -endTime "1457952053"
        -completionStatus "In Progress"
        -partTotalMatl  3.1626620
        -supportTotalMatl  0.0954441
        -partConsumed  1.5615234
        -supportConsumed  0.0926666
        -partTip "T12"
        -supportTip "T12"
        -partMatlName "NYL12"
        -supportMatlName "SR-110"
        -totalLayers 69
        -currentLayer 34
        -partMinX  11.465531
        -partMinY   0.058877
        -partMinZ   0.014056
        -partMaxX  16.191124
        -partMaxY  14.199600
        -partMaxZ   0.541973
        -comment "Packed file written by Control Center!!!!!Model color: black.  "
        -producer "10.6 (4739)"
        -buildMode ""
        -pack "{ sacTower,1140 CLIP, CAMERA FLEX ARM FLEX CLIP BODY*51 }"
};

set machineStatus(queue) {
};

set machineStatus(previousJob) {
        -position {-1}
        -jobName {}
        -jobId 0
        -owner {}
        -submitTime 0
        -estimatedBuildTime 0
        -startTime 0
        -endTime 0
        -buildTime 0
        -completionStatus {}
        -partMatlName {}
        -supportMatlName {}
        -partTotalMatl 0
        -supportTotalMatl 0
        -partConsumed 0
        -supportConsumed 0
        -partTip T16
        -supportTip T16
        -totalLayers 0
        -currentLayer 0
        -partMinX 0
        -partMinY 0
        -partMinZ 0
        -partMaxX 0
        -partMaxY 0
        -partMaxZ 0
        -comment {}
        -producer {}
        -pack {}
};

You can manipulate that file to set things like the jobName (current set to my-job) or jobid (currently set to 123). The system will parse this file and then update information about the printer, including creating build resources that represent what is being built

Here we’ll show the code for how to upload the file

# ... snip ...
def _create_modeler(modeler_type_uri):
    MODELER_DATA['type'] = modeler_type_uri
    return _make_request('POST', DATA_URL.format('modeler'), MODELER_DATA)

def _delete_modeler(modeler):
    _make_request('DELETE', modeler)

def _list_builds():
    return _make_request('GET', DATA_URL.format('build'))

def _create_capture(modeler, capture_type):
    payload = {
        'modeler'     : modeler['location'],
        'capture_type': capture_type
    }
    headers = _make_request('POST', DATA_URL.format('capture'), payload)
    capture_upload_url = headers["x-upload-location"]
    capture_uri = headers["Location"]
    return capture_uri, capture_upload_url

def _upload_capture(capture, upload_url, path):
    with open(path, 'rb') as f:
        content = gzip.compress(f.read())
        response = session.put(
            upload_url,
            data = content,
            headers = {'Content-Type': 'application/x-gzip'},
        )
    assert response.ok, response.text

def main():
    # authenticate
    _make_request('POST', USERS_URL, USER_DATA)

    modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/'
    capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/'

    # create the mode1ler
    modeler = _create_modeler(modeler_type)['location']
    pprint.pprint('Created modeler uri:{}'.format(modeler))

    # create a capture
    capture, upload_url = _create_capture(modeler, capture_type)

    # upload the status file
    _upload_capture(capture, upload_url, CAPTURE_PATH)

    # list builds while we wait for the processing
    try:
        while True:
            builds = _list_builds()
            if len(builds['resources']) > 0:
                pprint.pprint(builds)
            else:
                pprint.pprint('no builds')
            time.sleep(5)
    except KeyboardInterrupt:
        pass

    # optional clean up
    _delete_modeler(modeler['location'])

if __name__ == '__main__':
    main()

This will look for a file at CAPTURE_PATH to upload, read it, gzip it and then upload it to the url given when the capture was created. The capture file will then be processed. While we wait for the processing we loop and list the builds. When the capture is successfully processed we should see a new build in our list. This indicates that the file was processed successfully and 3DIAX has detected the build in our status file

The complete source code for our script is below

#!/usr/bin/env python3
import gzip
import pprint
import json
import requests
import time

"""change these global variables for your environment"""

DATA_URL         = 'https://data.authentise.com/{}/'
USERS_URL        = 'https://users.authentise.com/sessions/'
CAPTURE_PATH     = 'modeler_status_1.log'

HEADERS = {
    'Content-Type' : 'application/json',
    'Accept'       : 'application/json',
}
USER_DATA = {
    'username' : 'test@authentise.com',
    'password' : '******',
}

session = requests.Session()
def _make_request(method, url, payload=None, headers=HEADERS):
    response = session.request(method, url, json=payload, headers=HEADERS)
    if not response.ok:
        raise Exception("Bad response from {}: {}".format(url, response.json()))
    if method == 'POST':
        return response.headers
    elif method == 'PUT':
        return None
    elif method == 'DELETE':
        return None
    else:
        return response.json()

def _list_capture_types():
    return _make_request('GET', DATA_URL.format('capture-type'))


def _list_modeler_types():
    return _make_request('GET', DATA_URL.format('modeler-type'))

def _create_modeler(modeler_type_uri):
    payload = {
        'description'   : 'some fake modeler',
        'ip_address'    : '127.0.0.1',
        'name'          : 'test modeler',
        'serial_number' : '123',
        'type'          : modeler_type_uri,
    }
    return _make_request('POST', DATA_URL.format('modeler'), payload)

def _delete_modeler(modeler):
    _make_request('DELETE', modeler)

def _list_builds():
    return _make_request('GET', DATA_URL.format('build'))

def _get_capture_type_status_uri(modeler_types):
    for capture_types in modeler_types['resources']:
         for capture_type in capture_types['capture_types']:
             if capture_type['capture_type'] == 'catex.modeler.status':
                 return capture_types['uri'], capture_type['uri']

def _create_capture(modeler, capture_type):
    payload = {
        'modeler'     : modeler,
        'capture_type': capture_type
    }
    headers = _make_request('POST', DATA_URL.format('capture'), payload)
    capture_upload_url = headers["x-upload-location"]
    capture_uri = headers["Location"]
    return capture_uri, capture_upload_url

def _upload_capture(capture, upload_url, path):
    with open(path, 'rb') as f:
        content = gzip.compress(f.read())
        response = requests.put(
            upload_url,
            data=content,
            headers={"content-type": "application/x-gzip"}
        )
    assert response.ok, response.text

def main():
    # authenticate
    _make_request('POST', USERS_URL, USER_DATA)

    # show the different capture types
    #pprint.pprint(_list_capture_types())

    # show the different capture types
    #pprint.pprint(_list_modeler_types())

    modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/'
    capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/'

    # create the mode1ler
    modeler = _create_modeler(modeler_type)['location']
    pprint.pprint('Created modeler uri:{}'.format(modeler))

    # create a capture
    capture, upload_url = _create_capture(modeler, capture_type)
    pprint.pprint('Created capture: {} with upload URL {}'.format(capture, upload_url))

    # upload the status file
    _upload_capture(capture, upload_url, CAPTURE_PATH)

    # list builds while we wait for the processing
    try:
        while True:
            builds = _list_builds()
            if len(builds['resources']) > 0:
                pprint.pprint(builds)
            else:
                pprint.pprint('no builds')
            time.sleep(5)
    except KeyboardInterrupt:
        pass

    # optional clean up
    _delete_modeler(modeler)
    print('Deleted modeler {}'.format(modeler))

if __name__ == '__main__':
    main()