Access the 3D Spark Platform via API

The 3D Spark platform maybe accessed via REST API without using a visual interface. This allows third-party developers to utilize 3D Sparks algorithms from within their own software.

Requirements

If you lack any of these, feel free to contact us.

Workflow

By concept, all data is transferred in JSON format. The CAD file (STEP or STL) is appended along with necessary meta data in the requests body. The server will respond with a part ID, which can be used to retrieve analysis results. These are the necessary steps:

  1. Get an authentication token (Bearer) from the 3D Spark identity provider
  2. Get a session cookie (CSRF token) from the platform
  3. Upload file and meta data to the platform
  4. Poll for results
1. Authentication Token

The authentication token is essential for making requests to the platform and connected databases. It contains information about the requesting user and his permissions. The authentication token maybe used for multiple requests until it expires and has to be requested again. You may request an authentication token using this python example and your credentials:

  • USERNAME
  • PASSWORD
  • BACKEND_ID
  • BACKEND_SECRET
import requests
import json

# Create request data
form_data = {
    'grant_type': 'password',
    'username': USERNAME,
    'password': PASSWORD,
    'client_id': BACKEND_ID,
    'client_secret': BACKEND_SECRET

}

# Execute request
LOGIN_URL = 'https://auth.3dspark.de/realms/3dspark/protocol/openid-connect/token'
response = requests.post(LOGIN_URL, data=form_data)

# Get authentication_token from response
response_content = json.loads(response.content)
authentication_token = response_content['access_token']

print(authentication_token)
>>> "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwi..."
2. Session Cookie (CSRF Token)

The session token or session cookie is required to prevent access from unauthorized origins. Similar to the authentication token, this cookie may be reused.

# Request session cookie
SESSION_URL = 'https://launchpad.3dspark.de/api-auth/login'
response = requests.get(SESSION_URL)
session_cookie = response.cookies['csrftoken']

print(session_cookie)
>>> "bl7X5OMiu515qEjpFsQgqLPwS5uD366V7oLOGHZ5WRfY..."
3. Upload CAD File and Meta Data

For uploading the file it is encoded and the data is appended in the files section of the request. Meta data like lot size, material, process etc. is added as form data. For authentication the header section provides the authentication token and session cookie. Additionally the cookie has to be explicitly set.

# Encode STL or STEP file
CAD_FILE_PATH = "my_model.stl"
files = {
    'file': open(CAD_FILE_PATH, 'rb')  # Encode file
}

# Prepare meta data
form_data = {
    "start_analysis": "true",   # Trigger analysis after upload
    "mat_name": "316L",         # Material name
    "prc_acronym":"slm"         # Process Acronym
}

# Prepare authentication headers
headers = {
    'Authorization': 'Bearer ' + authentication_token,
    'X-CSRFToken': session_cookie,
}

# Prepare cookie
cookies = {
    'csrftoken': session_cookie
}

# Post data to platform
PART_API_URL = f'https://launchpad.3dspark.de/api/v1/part/'
response = requests.post(PART_API_URL, data=form_data, files=files, headers=headers, cookies=cookies)

print(response.content)
# >>> b'{"part_id": "0969919be77e48cfb82706d2c320adfd", "task": "upload_part", "message": "", "basename": "my_model.stl", "name": "my model"}'

# Get part ID
part_id = response.content['part_id']
3. Poll Results

Depending on the part complexity the analysis may take a few seconds up to a few minutes. The status of the analysis may be polled in time intervals of 30 seconds. Keep in mind to limit the number of requests and cancel polling after 5 minutes. Otherwise the user will be automatically blocked.

# Prepare authentication headers
headers = {
    'Authorization': 'Bearer ' + authentication_token,
    'X-CSRFToken': session_cookie,
}

# Prepare cookie
cookies = {
    'csrftoken': session_cookie
}

RESULTS_URL = 'https://launchpad.3dspark.de/api/v1/analysisResult/' + part_id + '/'
response = requests.get(RESULTS_URL, headers=headers, cookies=cookies)

print(response.content)

As long as the analysis is still running, the response content will look like this:

{
    "analysis_finished": false,
    "analysis_report": null
}

As soon as results are available, the full analysis data will be returned:

{
    "analysis_finished": true,
    "analysis_report": {
        "assets": {},
        "basename": "my_model.stl",
        "bb_x": 86.50474166870117,
        "bb_y": 109.58368682861328,
        "bb_z": 91.1187717244029,
        "cad_stat": {...},
        "categories": {
            "combo_categories": {...},
            "text_categories": {...}
        },
        "cost_saved": 4980.216590751541,
        "created_date": "11.11.2022",
        "creator": "arnd",
        "current_cost": {
            "calculated": 6250.469999999999,
            "used": null,
            "user": null
        },
        "current_leadtime": {
            "calculated": 15,
            "used": null,
            "user": null
        },
        "current_mat": "",
        "current_technology": "milling",
        "gap_min": 49.304962158203125,
        "info": "",
        "is_assembly": false,
        "leadtime_saved": 5,
        "loc_bat": false,
        "lot_size": 1,
        "market_prices_api": {},
        "mesh_checks": {
            "actions_performed_on_initial_mesh": {
                "Fixing Winding": {
                    "check_passed": null,
                    "suggested_user_action": "Not required.",
                    "tooltip_hover_text": ""
                },
                "Inverting Flipped Triangles": {
                    "check_passed": null,
                    "suggested_user_action": "Not required.",
                    "tooltip_hover_text": ""
                },
                "Mesh Size Reduction": {
                    "check_passed": null,
                    "suggested_user_action": "Not required.",
                    "tooltip_hover_text": ""
                }
            },
            "analysis_mesh": {
                "Valid Volume": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                },
                "Watertight": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                },
                "Winding Consistent": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                }
            },
            "upload_file": {
                "Unambiguous Mesh": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": "Fails if there are multiple (possibly different) meshes mapping the same geometry."
                },
                "Valid Volume": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                },
                "Watertight": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                },
                "Winding Consistent": {
                    "check_passed": true,
                    "suggested_user_action": "",
                    "tooltip_hover_text": ""
                }
            }
        },
        "min_bb_x": 79.19655636210463,
        "min_bb_y": 90.68146499756651,
        "min_bb_z": 92.43813674371825,
        "name": "my model",
        "parent_assembly": null,
        "part_id": "ec1deb0e996c4ba5b11b6d2171ad281f",
        "part_srf_area": 269.7284221212281,
        "part_stat": "unset",
        "part_vol": 245.16883341131197,
        "primary_process_chain_id": "5881569a6b0d4afa808948e536084c9f",

        "process_chains": {
            "436dc3a6fd1641928d6cbd157e326f13": {...},
            "5881569a6b0d4afa808948e536084c9f": {
                "bld_h": 94.29829984144529,
                "break_even": {...},
                "cost": {
                    "cost_pj": 1199.329073782251,
                    "cost_pl": 1199.334073782251,
                    "cost_pp": 1199.334073782251
                },
                "cost_bd": {
                    "cost_bd_pp_main": 988.1011931014809,
                    "cost_bd_pp_mat": 130.44121401410348,
                    "cost_bd_pp_post": 35.57,
                    "cost_bd_pp_pre": 45.23
                },
                "cost_ini_pp": 1270.2534092484589,
                "costing_config_id": "7ce7eff774cf46c790ea9dc98644ecbe",
                "feasibility": {
                    "gap_size": {
                        "gap_lim_min": 0.5,
                        "gap_min": 49.304962158203125,
                        "gap_min_chk": true
                    },
                    "size": {
                        "fits_mac": true,
                        "size_lim_max": 1000,
                        "size_lim_min": 7,
                        "size_max": 92.43813674371825,
                        "size_max_chk": true,
                        "size_min": 79.19655636210463,
                        "size_min_chk": true
                    },
                    "thickness": {
                        "t_lim_min": 0.5,
                        "t_max": 67.98809051513672,
                        "t_max_chk": null,
                        "t_min": 54.72135543823242,
                        "t_min_chk": true
                    }
                },
                "fits_mac": true,
                "float_feasibilities": {
                    "economic": 100,
                    "leadtime": 99.20634920634922,
                    "overall": 99.73544973544973,
                    "technical": 100
                },
                "has_scaling": false,
                "labor": {
                    "labor_pj": "23:28",
                    "labor_pl": "23:28",
                    "labor_pp": "23:28"
                },
                "leadtime": 16,
                "mac_id": "8ab36fd1d20b49379ffee898cf2883bf",
                "mac_t": {
                    "mac_t_pj": "355:53",
                    "mac_t_pl": "355:53",
                    "mac_t_pp": "355:53"
                },
                "machine_bld_size_x": 250,
                "machine_bld_size_y": 500,
                "machine_bld_size_z": 500,
                "machine_name": "SLM 500",
                "margin_user_value": null,
                "market_price": {
                    "market_price_pj": {
                        "calculated": 1798.9936106733765,
                        "user": null
                    },
                    "market_price_pl": {
                        "calculated": 1799.0011106733764,
                        "user": null
                    },
                    "market_price_pp": {
                        "calculated": 1799.0011106733764,
                        "user": null
                    }
                },
                "mat_id": "d8a93dd106de48b1ad299437ff13a28e",
                "material_name": "316L",
                "n_lyr": 2096,
                "name": "SLM | 316L",
                "nst_style": "alone",
                "opt_style": "alpha_beta_gamma",
                "or_bb_x": 80.5916910044279,
                "or_bb_y": 94.04364245094841,
                "or_bb_z": 89.29829984144529,
                "part_mass": 1949.09222561993,
                "parts_pj": 15,
                "prc": "Selective Laserbeam Melting",
                "prc_acronym": "slm",
                "process_chain_id": "5881569a6b0d4afa808948e536084c9f",
                "process_chain_stat": {...},
                "process_steps": {...},
                "rot_cost_euler": {
                    "x": -58.16303483330967,
                    "y": -20.67073524540798,
                    "z": -168.2807608511687
                },
                "rot_x_cost": 240.4245544829421,
                "rot_y_cost": 179.44032677965492,
                "rot_z_cost": 23.6283174033407,
                "scale": [
                    1,
                    1,
                    1
                ],
                "scaled_bounding_box": [
                    86.50474166870117,
                    109.58368682861328,
                    91.1187717244029
                ],
                "scaled_minimal_bounding_box": [
                    79.19655636210463,
                    90.68146499756651,
                    92.43813674371825
                ],
                "scaled_oriented_bounding_box": [
                    80.5916910044279,
                    94.04364245094841,
                    89.29829984144529
                ],
                "scaled_part_volume": 245.16883341131197,
                "sup_mass": 24.11024753386994,
                "sup_srf_area": 14.24525862152444,
                "sup_vol_full": 30.32735538851565,
                "sup_vol_real": 3.032735538851565,
                "tech": "additive_manufacturing"
            },
            "bca506483c894b119c4243ef8c78a19b": {}
        },
        "t_min": 54.72135543823242,
        "upload_file_units": "mm"
    }
}

The response contains all info about the part and the primary process chain (in general the AM process chain). The info can be easily extracted:

results = json.loads(response.content)['analysis_report']

# Get part info
part_name = results['name']  # "my model"
bounding_box_x = results['bb_x']  # 86.50474166870117

# Get primary process chain info (AM)
primary_process_chain_id = results['primary_process_chain_id']
primary_process_chain = results['process_chains'][primary_process_chain_id]

cost_per_part = primary_process_chain['cost']['cost_pp']  # 1199.334073782251
material_name = primary_process_chain['material_name']  # 1199.334073782251
fits_machine = primary_process_chain['feasibility']['size']['fits_mac']  # True
wall_thickness_check = primary_process_chain['feasibility']['thickness']['t_min_chk']  # True

Simplified Result

If you do not need the complete list of results, but only the most important aspects, you can poll simplified results as well.

# Prepare authentication headers
headers = {
    'Authorization': 'Bearer ' + authentication_token,
    'X-CSRFToken': session_cookie,
}

# Prepare cookie
cookies = {
    'csrftoken': session_cookie
}

# Use api/v1/analysisResult/simplified/
RESULTS_URL = 'https://launchpad.3dspark.de/api/v1/analysisResult/simplified/' + part_id + '/'
response = requests.get(RESULTS_URL, headers=headers, cookies=cookies)

print(response.content)

The response contains a simplified version of the results:

{
    "part_id": "0969919be77e48cfb82706d2c320adfd",
    "bb_x": 86.50474166870117,
    "bb_y": 109.58368682861328,
    "bb_z": 91.1187717244029,
    "part_vol": 245.16883341131197,
    "part_srf_area": 269.7284221212281,
    "cost_pp": 1199.334073782251,
    "thickness": true,
    "gap_size":  true,
    "fits_mac":  true,
    "market_price_pp": 1799.0011106733764
}