Formlabs Developers
Download PreForm Insider to start developing with the PreForm API.
Version v1.0.0-alpha (latest)

Modifying Models

Endpoints that take actions on models within a scene.

Scale models#

POST/v1/scene/model/scale/

Set a uniform scale factor on one or more models. scale replaces each model's current scale. It is not multiplied against the model's existing scale.

Scaling a model does not re-validate the layout. A model may end up outside the build volume, which /v1/scene/model/validate/in-bounds/ checks, or overlapping another model, which /v1/scene/model/validate/interferences/ checks.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body does not match the request schema: scene_id or scale missing, a field has the wrong type, or an unknown field is present. The message includes a JSON pointer to the offending field.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene.

Request bodyScaleRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to scale. Omitted or empty means every model in the scene.

  • scalenumberRequired

    The scale factor to apply. Replaces each model's current scale outright. It is not multiplied against the existing value.

    Greater than 0.

ReturnsModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

The models were scaled. Reports which models were scaled.

POST/v1/scene/model/scale/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.scale_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        scale=1.5,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/scale/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "scale": 1.5
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.scale_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        scale=0.5,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/scale/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "scale": 0.5
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ]
}

Orient models#

POST/v1/scene/model/orient/

Set the absolute orientation of one or more models, replacing their current orientation (not a relative rotation). Provide orientation as exactly one of: Euler angles, a 3x3 rotation matrix, or a pair of direction vectors. The response always reports the resulting orientation as Euler angles, regardless of which form was used in the request.

Orienting a model does not re-validate the layout. A model may end up outside the build volume, which /v1/scene/model/validate/in-bounds/ checks, or overlapping another model, which /v1/scene/model/validate/interferences/ checks.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body does not match the request schema: scene_id or orientation missing, orientation matches none of its three shapes, a field has the wrong type, or an unknown field is present.
400INPUT_ERRORscene_id is not a well-formed UUID or no scene with that id exists, or orientation.linear is not a pure rotation matrix.
404NOT_FOUNDA model in model_ids does not exist in the scene.

Request bodyOrientRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to orient. Omitted or empty means every model in the scene.

  • orientationEulerAngles or TransformMatrix or DirectionVectorsRequired

    A model orientation, given as exactly one of: Euler angles, a transform matrix, or a pair of direction vectors.

    Show child attributes
    • EulerAnglesoption 1 of 3

      Orientation as Euler angles in degrees. Rotations are applied in the order z, then x, then y.

      Show fields
      • xnumberRequired

        Rotation in degrees around the x axis (applied second).

      • ynumberRequired

        Rotation in degrees around the y axis (applied last).

      • znumberRequired

        Rotation in degrees around the z axis (applied first).

    • TransformMatrixoption 2 of 3

      Orientation as the linear (rotation/scale) part of a 3x3 transform matrix.

      Show fields
      • lineararray of array of numbersRequired

        A 3x3 matrix, given as 3 rows of 3 numbers.

        Exactly 3 items.

    • DirectionVectorsoption 3 of 3

      Orientation as two vectors, in model space, that say which way is "up" and "right" once the model is placed in the scene.

      Show fields
      • z_directionarray of numbersRequired

        Unit vector in model space that will point up (+Z) in the scene.

        Exactly 3 items.

      • x_directionarray of numbersRequired

        Unit vector in model space, perpendicular to z_direction, that will point right (+X) in the scene.

        Exactly 3 items.

ReturnsModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

The models were oriented. Reports which models were oriented.

POST/v1/scene/model/orient/
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import EulerAngles

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        orientation=EulerAngles(x=0, y=0, z=30),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "orientation": {
      "x": 0,
      "y": 0,
      "z": 30
    }
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import TransformMatrix

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        orientation=TransformMatrix.from_dict({
            "linear": [[0.70710678118, -0.70710678118, 0], [0.70710678118, 0.70710678118, 0], [0, 0, 1]],
        }),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "orientation": {
      "linear": [
        [
          0.70710678118,
          -0.70710678118,
          0
        ],
        [
          0.70710678118,
          0.70710678118,
          0
        ],
        [
          0,
          0,
          1
        ]
      ]
    }
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import DirectionVectors

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        orientation=DirectionVectors(z_direction=[0, 0, 1], x_direction=[1, 0, 0]),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "orientation": {
      "z_direction": [
        0,
        0,
        1
      ],
      "x_direction": [
        1,
        0,
        0
      ]
    }
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ]
}

Translate models#

POST/v1/scene/model/translate/

Move one or more models to a new position, either absolutely (place the model origin at the given coordinates) or relatively (offset each model from where it currently is).

Positions are in the scene's user coordinate space, in millimeters: x/y are horizontal with (0, 0) at the center of the build platform, and z is vertical with 0 at the platform surface. On SLA scenes the z component is ignored. Models stay attached to the build platform (or their raft height).

Translating a model does not re-validate the layout. A model may be moved outside the build volume, which /v1/scene/model/validate/in-bounds/ checks, or into another model, which /v1/scene/model/validate/interferences/ checks.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body does not match the request schema: scene_id or position missing, a field has the wrong type, mode is not one of the documented values, or an unknown field is present. The message includes a JSON pointer to the offending field.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene.

Request bodyTranslateRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to translate. Omitted or empty means every model in the scene.

  • positionScenePositionRequired

    A position within a printer's build volume, in millimeters, in user coordinate space.

    Show child attributes
    • xnumberRequired

      X position. 0 is the center of the build volume. Positive moves right as you face the printer.

    • ynumberRequired

      Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

    • znumberRequired

      Vertical position. 0 is the surface of the build platform.

  • How to interpret the position of a translate request.

    Defaults to "ABSOLUTE"

    One of:

    ABSOLUTE
    Set each model's origin to exactly the given coordinates. Translating several models to the same absolute position stacks them.
    RELATIVE
    Add the given offset to each model's current position, preserving the models' arrangement relative to each other.

ReturnsModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

The models were translated. Reports which models were moved.

POST/v1/scene/model/translate/
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import ScenePosition, TranslateMode

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.translate_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        position=ScenePosition(x=30, y=-15, z=0),
        mode=TranslateMode("ABSOLUTE"),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/translate/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "position": {
      "x": 30,
      "y": -15,
      "z": 0
    },
    "mode": "ABSOLUTE"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import ScenePosition, TranslateMode

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.translate_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        position=ScenePosition(x=0, y=25, z=0),
        mode=TranslateMode("RELATIVE"),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/translate/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "position": {
      "x": 0,
      "y": 25,
      "z": 0
    },
    "mode": "RELATIVE"
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ]
}

Automatically orient models#

POST/v1/scene/model/auto-orient/

Reorients one or more models to the orientation PreForm estimates will produce the best print quality on the scene's printer. Pass mode: "DENTAL" to use the dental-specific algorithm instead, which tilts the appliance's occlusal (biting) plane by tilt degrees (default 0) rather than searching for a quality-optimized orientation.

A reoriented model can end up outside the build volume or overlapping another model. Check with /v1/scene/model/validate/in-bounds/ and /v1/scene/model/validate/interferences/.

This can take a while, especially with the default algorithm across many models, so prefer ?async=true and poll the returned operation.

Unlike /v1/scene/model/scale/ and /v1/scene/model/orient/, an unknown id in model_ids is reported as OPERATION_FAILED rather than NOT_FOUND.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body matches neither request shape: scene_id is missing, mode is present but not "DENTAL", tilt is present without mode: "DENTAL", a field has the wrong type, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
400OPERATION_FAILEDA model in model_ids does not exist in the scene, or the auto-orient algorithm itself failed. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyobject

  • Defaultoption 1 of 2
    Show fields
    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to auto-orient. Omitted or empty means every model in the scene.

  • Dental Modeoption 2 of 2
    Show fields
    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to auto-orient. Omitted or empty means every model in the scene.

    • Selects a specialized auto-orient algorithm.

      One of:

      DENTAL
      Orients for dental appliance printing by tilting the occlusal (biting) plane, rather than searching for a quality-optimized orientation.
    • tiltinteger

      Degrees to tilt the occlusal plane. Only applies when mode is "DENTAL".

      Defaults to 0

Returns 200 OKModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

The models were auto-oriented. Reports which models were reoriented.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/auto-orient/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ]
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import AutoOrientMode

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_orient_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        mode=AutoOrientMode("DENTAL"),
        tilt=30,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "mode": "DENTAL",
    "tilt": 30
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
    "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Generate supports for models#

POST/v1/scene/model/auto-support/

Computes and attaches print supports to one or more models, using the scene's current support settings as defaults for any field omitted from the request. Existing supports on a targeted model are replaced.

Supports and their raft count toward whether a model fits, so a model that fit before can extend past the build volume, or into another model, once it is supported. Check with /v1/scene/model/validate/in-bounds/ and /v1/scene/model/validate/interferences/.

only_minima generates only the touchpoints a model strictly needs to print (a faster, reduced-fidelity pass) instead of the full support structure.

breakaway_structure_enabled is only accepted when the scene's material supports breakaway supports. Otherwise it is rejected with INPUT_ERROR.

This can take a while across many models, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, a numeric field is negative, raft_type is outside its enum, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID or no scene with that id exists, or breakaway_structure_enabled was given but the scene's material doesn't support breakaway supports.
400OPERATION_FAILEDA model in model_ids does not exist in the scene, the scene's printer type doesn't support generating supports, a model is hidden/not printable or too large to support, or support generation itself failed. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyAutoSupportRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to auto-support. Omitted or empty means every model in the scene.

  • densitynumber

    Support touchpoint density.

    At least 0.

  • slope_multipliernumber

    Scales how aggressively steep overhangs are supported.

    At least 0.

  • touchpoint_size_mmnumber

    Diameter of each support touchpoint.

    At least 0.

  • raft_thickness_mmnumber

    Thickness of the raft under the supports.

    At least 0.

  • height_above_raft_mmnumber

    Vertical gap left between the raft and the model.

    At least 0.

  • z_compression_correction_mmnumber

    Raft thickness compensation applied for print compression.

    At least 0.

  • early_layer_merge_mmnumber

    Height over which nearby touchpoints are merged into wider bases.

    At least 0.

  • only_minimaboolean

    Generate only the touchpoints the model strictly needs to print (a faster, reduced-fidelity pass) instead of the full support structure.

    Defaults to false

  • raft_typeenum RaftType

    Which raft style to build under a model's supports.

    One of:

    FULL_RAFT
    A single solid raft under the whole support structure.
    MINI_RAFT
    A small raft under each touchpoint cluster instead of one solid raft.
    MINI_RAFTS_ON_BP
    Mini rafts placed directly on the build platform rather than under the touchpoints.
  • raft_label_enabledboolean

    Engrave the model's name on its raft. The text is fixed when the supports are generated: renaming the model afterwards does not change it until the supports are regenerated. Pass false for a raft whose geometry does not depend on the model's name.

  • breakaway_structure_enabledboolean

    Use breakaway supports. Only accepted when the scene's material supports this option.

  • internal_supports_enabledboolean

    Generate internal supports for enclosed cavities.

Returns 200 OKModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

Supports were generated. Reports which models were supported.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/auto-support/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_support_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-support/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import RaftType

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_support_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        density=1.2,
        raft_type=RaftType("MINI_RAFT"),
        only_minima=False,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-support/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "density": 1.2,
    "raft_type": "MINI_RAFT",
    "only_minima": false
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Rebuild existing supports in place#

POST/v1/scene/model/regenerate-supports/

Rebuilds the support structure of models that already have supports, keeping their touchpoints and the support settings they were generated with. Only what is derived from the model itself changes; in particular a raft generated with raft_label_enabled is re-engraved with the model's current name. This is how to clear the RAFT_LABEL_OUTDATED warning /v1/scene/model/update/ reports after a rename without recomputing touchpoints. To change settings or touchpoints instead, use /v1/scene/model/auto-support/.

Omitting model_ids (or sending an empty array) selects every model in the scene that has supports. Listing a model without supports is an error; nothing is regenerated in that case.

Rebuilt supports and their raft count toward whether a model fits, so a regenerated model can extend past the build volume or into another model. Check with /v1/scene/model/validate/in-bounds/ and /v1/scene/model/validate/interferences/.

Regeneration skips touchpoint finding, the most expensive part of /v1/scene/model/auto-support/, so it is much cheaper than generating supports from scratch. Rebuilding the support structure still takes noticeable time for large models or many models, so prefer ?async=true and poll the returned operation. If any regeneration fails, the whole request fails and no model is changed.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, model_ids is not an array of strings, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, no scene with that id exists, or a model in model_ids has no supports to regenerate. No models were modified.
404NOT_FOUNDA model in model_ids does not exist in the scene. No models were modified.
400OPERATION_FAILEDSupport generation itself failed for a model. No models were modified. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyRegenerateSupportsRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models whose supports to regenerate. Omitted or empty means every model in the scene that has supports.

Returns 200 OKModifiedModels

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the operation was applied to.

The supports were regenerated. Reports which models were regenerated.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/regenerate-supports/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.regenerate_supports(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/regenerate-supports/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ]
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.regenerate_supports(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/regenerate-supports/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Automatically arrange models on the build platform#

POST/v1/scene/model/auto-layout/

Repositions and rotates one or more models to fit them on the build platform. This endpoint is for SLA build platforms. Use /v1/scene/model/auto-pack/ for SLS scenes.

Pass mode: "DENTAL" to temporarily use the scene's dental workspace settings for the layout instead of the standard ones.

By default (on_layout_failure: "FAIL_AND_LEAVE_SCENE_UNCHANGED"), a layout that can't fit every requested model fails the whole request and leaves the scene unchanged. Set on_layout_failure: "EJECT_A_MODEL_AND_RETRY" to instead have the algorithm greedily remove seeded-random models from the scene, retrying until the rest fit (capped by max_ejections, if given). The response reports the outcome only: laid_out_model_ids and ejected_model_ids, in the request's model_ids order. ejected_model_ids is empty unless a model was actually ejected. Fetch /v1/scene/model/list/ afterwards for the resulting poses.

Passing seed makes the chosen arrangement reproducible across calls with the same inputs. If seed is given without layout_attempts/iterations_per_attempt, both are pinned to fixed defaults so results stay deterministic. Without seed, a random seed is used and results vary across calls.

This can take a while, especially with many models or high layout_attempts/iterations_per_attempt, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, mode/on_layout_failure is outside its enum, custom_bounds is missing one of its four fields, seed exceeds the uint32 range, layout_attempts/iterations_per_attempt is outside its range, max_ejections is present without on_layout_failure: "EJECT_A_MODEL_AND_RETRY" or is not a positive integer, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
400OPERATION_FAILEDA model in model_ids does not exist in the scene, the scene's printer type doesn't support auto-layout, or (only in FAIL_AND_LEAVE_SCENE_UNCHANGED mode) not every requested model fits within the build volume. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyAutoLayoutRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to lay out. Omitted or empty means every model in the scene.

  • Which workspace's layout parameters to use for this call.

    Defaults to "DEFAULT"

    One of:

    DEFAULT
    Use the scene's standard layout parameters.
    DENTAL
    Temporarily use the scene's dental workspace parameters for this call, then restore the scene's previous parameters afterwards.
  • model_spacing_mmnumber

    Minimum gap to leave between models.

    At least 0.

    Defaults to 1

  • allow_overlapping_supportsboolean

    Allow one model's supports to intersect another model or its supports.

    Defaults to false

  • lock_rotationboolean

    Only translate models and never rotate them, regardless of their individual lock.

    Defaults to false

  • custom_boundsLayoutBounds

    A custom rectangular region of the build platform to lay out within, in user XY coordinates. All four fields are required together.

    Show child attributes
    • x_min_mmnumberRequired
    • x_max_mmnumberRequired
    • y_min_mmnumberRequired
    • y_max_mmnumberRequired
  • seedinteger

    Random seed controlling the layout algorithm. Given the same models and settings, the same seed reproduces the same arrangement. Omitted means a random seed is used each call.

    At least 0, at most 4294967295.

  • layout_attemptsinteger

    Number of independent layout attempts to try, keeping the best. Defaults to an algorithm-chosen value, or 200 when seed is set without an explicit value (to keep seeded results deterministic).

    At least 1, at most 2500.

  • iterations_per_attemptinteger

    Optimization iterations per attempt. Defaults to an algorithm-chosen value, or 25000 when seed is set without an explicit value (to keep seeded results deterministic).

    At least 1, at most 250000.

  • on_layout_failureenum OnLayoutFailure

    What to do when the layout cannot fit every requested model.

    Defaults to "FAIL_AND_LEAVE_SCENE_UNCHANGED"

    One of:

    FAIL_AND_LEAVE_SCENE_UNCHANGED
    Fail the request with OPERATION_FAILED. The scene is left unchanged.
    EJECT_A_MODEL_AND_RETRY
    Greedily remove seeded-random models from the layout and retry until the rest fit, up to max_ejections removals. Ejected models are removed from the scene.
  • max_ejectionsinteger

    Maximum number of models that may be ejected before the layout fails outright. Only valid with on_layout_failure: "EJECT_A_MODEL_AND_RETRY". Omitted means no cap.

    At least 1.

Returns 200 OKAutoLayoutResponse

  • laid_out_model_idsarray of string (uuid)sRequired

    Requested models that remain in the scene.

  • ejected_model_idsarray of string (uuid)sRequired

    Requested models removed from the scene to make the rest fit. Empty unless on_layout_failure is EJECT_A_MODEL_AND_RETRY.

The models were laid out (and possibly ejected). Reports which requested models remain in the scene and which were ejected.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/auto-layout/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_layout_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-layout/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import OnLayoutFailure

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_layout_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10", "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"],
        seed=42,
        on_layout_failure=OnLayoutFailure("EJECT_A_MODEL_AND_RETRY"),
        max_ejections=1,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-layout/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
      "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
    ],
    "seed": 42,
    "on_layout_failure": "EJECT_A_MODEL_AND_RETRY",
    "max_ejections": 1
  }'
Response
JSON
{
  "laid_out_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ],
  "ejected_model_ids": []
}
JSON
{
  "laid_out_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ],
  "ejected_model_ids": [
    "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Automatically pack models within the build volume#

POST/v1/scene/model/auto-pack/

Packs the selected models within the build volume without duplicating them, using 3D nesting rather than 2D placement. Unselected models are left in place and act as fixed obstacles. This endpoint is for SLS printers. Use /v1/scene/model/auto-layout/ for SLA scenes.

The packer is greedy: it places models one at a time and carries on when one does not fit. on_pack_failure decides what happens to the models it could not place. With FAIL_AND_LEAVE_SCENE_UNCHANGED (the default) the request fails and the scene is left unchanged. With PARK_OUTSIDE_BUILD_VOLUME the packed models keep their new poses, the models that did not fit are parked beside the build volume, outside it, and the response names both groups, so a caller can drain a queue that is larger than one build: pack, keep what landed, carry the rest to the next scene.

The response reports the outcome only: packed_model_ids and unpacked_model_ids covering every selected visible model (hidden models are never packed, so they are not reported). Fetch /v1/scene/model/list/ afterwards for the resulting poses.

This can take a while, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, seed exceeds the uint32 range, distance_from_wall_mm is negative, packing_mode or on_pack_failure is outside its enum, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene.
400OPERATION_FAILEDThe scene's printer type doesn't support packing, or, under on_pack_failure: FAIL_AND_LEAVE_SCENE_UNCHANGED, not every model fits within the build volume. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyAutoPackRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to pack. Omitted or empty means every model in the scene.

  • model_spacing_mmnumber

    Minimum gap to leave between models. Floored at the material's visual collision offset if smaller.

    At least 0.

  • distance_from_wall_mmnumber

    Margin to leave between models and the build volume walls.

    At least 0.

  • seedinteger

    Random seed controlling the packing algorithm. Omitted means a random seed is used each call.

    At least 0, at most 4294967295.

  • packing_modeenum PackingMode

    Packing objective used to arrange models within the (SLS) build volume.

    Defaults to "PACK_HEIGHT"

    One of:

    PACK_HEIGHT
    Minimize the height the packed models occupy.
    PACK_VOLUME
    Minimize the volume the packed models occupy.
  • on_pack_failureenum OnPackFailure

    What to do with models the packer could not place inside the build volume.

    Defaults to "FAIL_AND_LEAVE_SCENE_UNCHANGED"

    One of:

    FAIL_AND_LEAVE_SCENE_UNCHANGED
    Fail the request with OPERATION_FAILED. The scene is left unchanged.
    PARK_OUTSIDE_BUILD_VOLUME
    Keep the pack. Models that fit take their packed poses; models that did not are parked beside the build volume, outside it, and reported in unpacked_model_ids. Nothing is removed from the scene.

Returns 200 OKAutoPackResponse

  • packed_model_idsarray of string (uuid)sRequired

    Selected visible models that ended inside the build volume.

  • unpacked_model_idsarray of string (uuid)sRequired

    Selected visible models the packer could not place. Empty unless on_pack_failure is PARK_OUTSIDE_BUILD_VOLUME.

The pack ran. Reports which of the selected models were placed inside the build volume and which were left outside it. Under FAIL_AND_LEAVE_SCENE_UNCHANGED, unpacked_model_ids is always empty here, because a pack that leaves anything outside fails instead.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/auto-pack/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_pack_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-pack/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import OnPackFailure

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_pack_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        on_pack_failure=OnPackFailure("PARK_OUTSIDE_BUILD_VOLUME"),
        seed=7,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-pack/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "on_pack_failure": "PARK_OUTSIDE_BUILD_VOLUME",
    "seed": 7
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import PackingMode

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.auto_pack_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        packing_mode=PackingMode("PACK_VOLUME"),
        distance_from_wall_mm=5,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/auto-pack/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "packing_mode": "PACK_VOLUME",
    "distance_from_wall_mm": 5
  }'
Response
JSON
{
  "packed_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
    "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
  ],
  "unpacked_model_ids": []
}
JSON
{
  "packed_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ],
  "unpacked_model_ids": [
    "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Fill the build platform with duplicates#

POST/v1/scene/model/fill-build-platform/

Duplicates the selected models and lays out the copies (using the same algorithm as /v1/scene/model/auto-layout/), adding as many as fit on the build platform. Unselected models are left in place and act as fixed obstacles. This endpoint is for SLA. Use /v1/scene/model/fill-build-chamber/ for SLS scenes.

Returns new_model_ids. Call /v1/scene/model/list/ afterwards to see the full scene result.

This can take a while, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, layout_attempts/iterations_per_attempt is outside its range, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
400OPERATION_FAILEDA model in model_ids does not exist in the scene, the scene's printer type doesn't support this operation, or not every duplicate fits within the build volume. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyFillBuildPlatformRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to duplicate. Omitted or empty means every model in the scene.

  • layout_optionsLayoutOptions

    SLA layout parameters shared by the fill-build-platform operation.

    Show child attributes
    • model_spacing_mmnumber

      Minimum gap to leave between models.

      At least 0.

      Defaults to 1

    • placement_margin_mmnumber

      Margin to leave between models and the build platform edge.

      At least 0.

      Defaults to 2.5

    • allow_overlapping_supportsboolean

      Allow one model's supports to intersect another model or its supports.

      Defaults to false

    • lock_rotationboolean

      Only translate models and never rotate them, regardless of their individual lock.

      Defaults to false

    • layout_attemptsinteger

      Number of independent layout attempts to try, keeping the best. Defaults to an algorithm-chosen value.

      At least 1, at most 2500.

    • iterations_per_attemptinteger

      Optimization iterations per attempt. Defaults to an algorithm-chosen value.

      At least 1, at most 250000.

Returns 200 OKNewModelIds

  • new_model_idsarray of string (uuid)sRequired

    Ids of the newly created models, in no particular order.

Duplicates were added and laid out. Returns the ids of the newly created models. The scene itself is not included.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/fill-build-platform/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.fill_build_platform(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/fill-build-platform/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import LayoutOptions

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.fill_build_platform(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        layout_options=LayoutOptions(model_spacing_mm=5),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/fill-build-platform/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "layout_options": {
      "model_spacing_mm": 5
    }
  }'
Response
JSON
{
  "new_model_ids": [
    "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
    "b2c3d4e5-f6a7-4890-b1c2-d3e4f5a6b7c8"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Fill the build chamber with duplicates#

POST/v1/scene/model/fill-build-chamber/

Duplicates the selected models and packs them (the same algorithm as /v1/scene/model/auto-pack/) up to fill_to_height_mm, in mm above the build platform. Unselected models are left in place and act as fixed obstacles. This endpoint is for SLS. Use /v1/scene/model/fill-build-platform/ for SLA scenes.

Returns new_model_ids. Call /v1/scene/model/list/ afterwards to see the full scene result.

This can take a while, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, fill_to_height_mm is not positive, seed exceeds the uint32 range, distance_from_wall_mm is negative, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene.
400OPERATION_FAILEDThe scene's printer type doesn't support packing, or not every duplicate fits within fill_to_height_mm. The message carries the underlying reason.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyFillBuildChamberRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to duplicate. Omitted or empty means every model in the scene.

  • packing_optionsPackingOptions

    SLS packing parameters shared by the fill-build-chamber operation.

    Show child attributes
    • model_spacing_mmnumber

      Minimum gap to leave between models. Floored at the material's visual collision offset if smaller.

      At least 0.

    • distance_from_wall_mmnumber

      Margin to leave between models and the build volume walls.

      At least 0.

    • seedinteger

      Random seed controlling the packing algorithm. Omitted means a random seed is used each call.

      At least 0, at most 4294967295.

  • fill_to_height_mmnumber

    Height, in mm above the build platform, to fill up to. Defaults to the scene's full build volume height.

    Greater than 0.

Returns 200 OKNewModelIds

  • new_model_idsarray of string (uuid)sRequired

    Ids of the newly created models, in no particular order.

Duplicates were added and packed. Returns the ids of the newly created models. The scene itself is not included.

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The request was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/fill-build-chamber/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.fill_build_chamber(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/fill-build-chamber/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import PackingOptions

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.fill_build_chamber(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        fill_to_height_mm=42,
        packing_options=PackingOptions(seed=12345),
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/fill-build-chamber/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "fill_to_height_mm": 42,
    "packing_options": {
      "seed": 12345
    }
  }'
Response
JSON
{
  "new_model_ids": [
    "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
    "b2c3d4e5-f6a7-4890-b1c2-d3e4f5a6b7c8"
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

Import a model#

POST/v1/scene/model/import/

Loads a 3D model file into the scene as a new model. Supported mesh formats are STL, OBJ, and 3MF, and supported CAD formats are STEP, Solidworks, PTC Creo, JT, Catia V5, Inventor, and Rhino. chordal_tolerance_um and angle_tolerance_deg control tessellation and only affect CAD imports. Saved PreForm projects (.form) are not model files and are rejected with INPUT_ERROR; open them as new scenes with /v1/scene/open/.

The model is placed using the file's own coordinates and is never moved away from models it overlaps; check for that with /v1/scene/model/validate/interferences/ after importing. The scene's build-volume and snapping rules apply, so on printers that snap to the build platform the model is dropped onto it, and a model outside the build volume is moved inside. A model too large to fit stays out of bounds; check with /v1/scene/model/validate/in-bounds/. Change its pose afterwards through the dedicated scale/orient/translate endpoints. name overrides the display name that would otherwise be derived from the file name.

Multi-part files import as one model. A CAD or 3MF file that holds several parts is imported as a single combined model unless the request sets split_multi_model_file: true.

With split_multi_model_file: true, new_models has one entry per created model, in unspecified order. The per-model overrides (name, lock) target a single model, so they are accepted only when the import creates exactly one model. When a split produces several parts the request fails with INPUT_ERROR and nothing is imported; drop the overrides or import without splitting.

The response identifies each new model by model_id but does not describe it. To read the imported models' properties, such as pose, bounding box, and in_bounds, call /v1/scene/model/list/ with the same scene_id and model_ids set to the model_id values from new_models. To read the whole scene instead, call /v1/scene/get/ with the scene_id. Both are POST requests that take their input as a JSON body:

JSON
POST /v1/scene/model/list/
{
  "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "model_ids": ["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"]
}

file must be a model file, matched on the extension regardless of case; the extension is checked before the ids.

Import can take a while for large or CAD files, so prefer ?async=true and poll the returned operation.

Error codes#

HTTPerror.codeCause
400INVALID_QUERY_PARAMETERasync is present but not true or false.
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id or file is missing or not a string, repair_behavior/units is outside its enum, or an unrecognized field is present.
400INPUT_ERRORfile's extension is not a supported model format (.form files are rejected here; open them with /v1/scene/open/), a per-model override (name, lock) was given and the file produced more than one model (nothing is imported), scene_id is not a well-formed UUID, or no scene with that id exists.
400OPERATION_FAILEDThe import failed for a reason not covered above: the file does not exist, is corrupt, holds no model, or repair_behavior is ERROR and the model needs repair.
500UNKNOWN_FAILUREAn unexpected exception occurred inside PreFormServer.

Query parameters

  • asyncboolean

    When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

    Defaults to false

Request bodyImportModelRequest

  • scene_idstring (uuid)Required

    The id of the scene to import into.

  • filestringRequired

    Full path to the file to load.

  • repair_behaviorenum RepairBehavior

    What to do when an imported file needs mesh repair.

    Defaults to "ERROR"

    One of:

    REPAIR
    Attempt to repair the model if needed before adding it to the scene.
    ERROR
    Fail the import with OPERATION_FAILED instead of importing a model that needs repair.
    IGNORE
    Import the model as-is, without repairing it.
  • namestring

    Display name for the new model. Defaults to a name derived from the file name.

  • The units to interpret the file's coordinates in.

    Defaults to "DETECTED"

    One of:

    DETECTED
    Use the file's own unit metadata when the format carries it (e.g. STEP, 3MF). Otherwise treat the file as millimeters.
    MILLIMETERS
    Treat the file's coordinates as millimeters, regardless of any unit metadata in the file.
    INCHES
    Treat the file's coordinates as inches, regardless of any unit metadata in the file.
  • Orientation and position constraints on a model, respected by automatic layout and packing operations.

    Defaults to "FREE"

    Currently one of the following; more may be added:

    FREE
    Layout operations may move and rotate the model freely.
    LOCKED_XY_FLIP_ENABLED
    Layout may move the model and spin it about the vertical (Z) axis, and may also flip it over; it may not tilt it otherwise.
    LOCKED_XY_ROTATION_FREE_TRANSLATION
    Layout may move the model and spin it about the vertical (Z) axis, but not tilt it.
    LOCKED_ROTATION_FREE_TRANSLATION
    Layout may move the model but not rotate it at all.
    FULLY_LOCKED
    Layout leaves the model exactly where it is.
  • chordal_tolerance_umnumber

    Tessellation chordal tolerance, in micrometers. Only affects CAD file imports.

    Defaults to 10

  • angle_tolerance_degnumber

    Tessellation angle tolerance, in degrees. Only affects CAD file imports.

    Defaults to 5

  • split_multi_model_fileboolean

    Whether a CAD or 3MF file holding several parts is imported as one model per part. Off by default: the parts arrive as a single combined model, and name and lock apply to it. No effect on STL or OBJ.

    Defaults to false

Returns 200 OKImportModelResult

  • new_modelsarray of ImportedModelsRequired

    One entry per created model, in no particular order. Exactly one entry unless split_multi_model_file split the file into several models. Use each entry's source_part_name / source_part_index, never its array position, to identify which part of the file it came from.

    Show child attributes
    • model_idstring (uuid)Required

      Id of the created model.

    • source_part_namestring

      The name of the part this model was created from, as read from the imported file, before any renaming applied by the scene or the request's name override. Parts with no name in the file get a generated name derived from the file name. Use source_part_index when part names cannot be trusted to identify parts.

    • source_part_indexinteger

      Zero-based position of the source part within the imported file, counting every part in the file (including parts that failed to import). A single-model import reports 0. This is the stable identity for split assemblies whose parts share a name.

The file was imported. new_models has one entry per created model (exactly one unless split_multi_model_file: true split the file into several).

Returns 202 AcceptedOperationAccepted

  • operation_idstring (uuid)Required

    Randomly generated id of the tracked operation.

  • operation_urlstringRequired

    Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

  • messagestringRequired

    Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

The import was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

Response headers (202)

  • Locationstring

    Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

POST/v1/scene/model/import/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.import_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        file="C:\\Models\\bracket.stl",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/import/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "file": "C:\\Models\\bracket.stl"
  }'
Python
from formlabs_sdk import FormlabsClient
from formlabs_preform_api.models import ImportUnits, RepairBehavior

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.import_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        file="C:\\Models\\bracket.step",
        repair_behavior=RepairBehavior("REPAIR"),
        name="bracket-v2",
        units=ImportUnits("MILLIMETERS"),
        lock="FREE",
        chordal_tolerance_um=10,
        angle_tolerance_deg=5,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/import/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "file": "C:\\Models\\bracket.step",
    "repair_behavior": "REPAIR",
    "name": "bracket-v2",
    "units": "MILLIMETERS",
    "lock": "FREE",
    "chordal_tolerance_um": 10,
    "angle_tolerance_deg": 5
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.import_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        file="C:\\Models\\assembly.step",
        split_multi_model_file=True,
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/import/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "file": "C:\\Models\\assembly.step",
    "split_multi_model_file": true
  }'
Response
JSON
{
  "new_models": [
    {
      "model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
      "source_part_name": "bracket",
      "source_part_index": 0
    }
  ]
}
JSON
{
  "new_models": [
    {
      "model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
      "source_part_name": "housing",
      "source_part_index": 0
    },
    {
      "model_id": "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55",
      "source_part_name": "base plate",
      "source_part_index": 1
    }
  ]
}
JSON
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
}

List models in a scene#

POST/v1/scene/model/list/

Returns the current properties of the models in a scene. Supply the optional model_ids array to restrict the response to those models.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORscene_id is missing, model_ids is present but not an array of strings, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene.

Request bodyListModelsRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to list. Omitted or empty means every model in the scene.

Returnsobject

  • modelsarray of ModelPropertiesRequired
    Show child attributes
    • idstring (uuid)Required

      Unique identifier of the model within its scene.

    • namestringRequired

      Display name used within job preparation.

    • positionScenePositionRequired

      A position within a printer's build volume, in millimeters, in user coordinate space.

      Show child attributes
      • xnumberRequired

        X position. 0 is the center of the build volume. Positive moves right as you face the printer.

      • ynumberRequired

        Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

      • znumberRequired

        Vertical position. 0 is the surface of the build platform.

    • orientationEulerAnglesRequired

      Orientation as Euler angles in degrees. Rotations are applied in the order z, then x, then y.

      Show child attributes
      • xnumberRequired

        Rotation in degrees around the x axis (applied second).

      • ynumberRequired

        Rotation in degrees around the y axis (applied last).

      • znumberRequired

        Rotation in degrees around the z axis (applied first).

    • scalenumberRequired

      Scale factor applied to the model.

    • unitsenum UnitsRequired

      The measurement units a model was defined in.

      Currently one of the following; more may be added:

      MILLIMETERS
      Model coordinates are millimeters.
      INCHES
      Model coordinates are inches.
    • bounding_boxobjectRequired

      Axis-aligned bounding box of the model (including generated geometry such as supports) in scene coordinates. Group containers have an empty bounding box, serialized as all zeros.

      Show child attributes
      • min_cornerScenePosition

        A position within a printer's build volume, in millimeters, in user coordinate space.

        Show child attributes
        • xnumberRequired

          X position. 0 is the center of the build volume. Positive moves right as you face the printer.

        • ynumberRequired

          Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

        • znumberRequired

          Vertical position. 0 is the surface of the build platform.

      • max_cornerScenePosition

        A position within a printer's build volume, in millimeters, in user coordinate space.

        Show child attributes
        • xnumberRequired

          X position. 0 is the center of the build volume. Positive moves right as you face the printer.

        • ynumberRequired

          Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

        • znumberRequired

          Vertical position. 0 is the surface of the build platform.

    • original_filestringRequired

      Path of the file the model was imported from.

    • visiblebooleanRequired

      Whether the model is currently shown in the scene.

    • has_supportsbooleanRequired

      Whether support structures have been generated for the model.

    • is_groupbooleanRequired

      Whether this entry is a Part Group container rather than a regular printable model. Group containers have zero volume and an empty bounding box.

    • is_cagebooleanRequired

      Whether the group is a cage. Cages are a subset of groups, so is_cage: true implies is_group: true.

    • in_boundsbooleanRequired

      Whether the model lies entirely within the build volume.

    • raw_mesh_hashstringRequired

      Hash of the raw mesh data, before any transform.

    • canonical_model_hashstringRequired

      Hash of the mesh data together with its position, orientation, and scale.

    • lockenum LockRequired

      Orientation and position constraints on a model, respected by automatic layout and packing operations.

      Defaults to "FREE"

      Currently one of the following; more may be added:

      FREE
      Layout operations may move and rotate the model freely.
      LOCKED_XY_FLIP_ENABLED
      Layout may move the model and spin it about the vertical (Z) axis, and may also flip it over; it may not tilt it otherwise.
      LOCKED_XY_ROTATION_FREE_TRANSLATION
      Layout may move the model and spin it about the vertical (Z) axis, but not tilt it.
      LOCKED_ROTATION_FREE_TRANSLATION
      Layout may move the model but not rotate it at all.
      FULLY_LOCKED
      Layout leaves the model exactly where it is.

Returns the requested models, in the order given by model_ids. If model_ids is omitted or empty, returns every model in the scene, in a deterministic but otherwise arbitrary order.

POST/v1/scene/model/list/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.list_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/list/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.list_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/list/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ]
  }'
Response
JSON
{
  "models": [
    {
      "id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
      "name": "bracket.stl",
      "position": {
        "x": 0,
        "y": 0,
        "z": 5.2
      },
      "orientation": {
        "x": 0,
        "y": 0,
        "z": 0
      },
      "scale": 1,
      "units": "MILLIMETERS",
      "bounding_box": {
        "min_corner": {
          "x": -17.9,
          "y": -32.9,
          "z": 0
        },
        "max_corner": {
          "x": 17.9,
          "y": 2.9,
          "z": 41.7
        }
      },
      "original_file": "C:\\Models\\bracket.stl",
      "visible": true,
      "has_supports": true,
      "is_group": false,
      "is_cage": false,
      "in_bounds": true,
      "raw_mesh_hash": "5f2b0c9a8d3e41f7",
      "canonical_model_hash": "9c81f0b2a6d45e37",
      "lock": "FREE"
    }
  ]
}

Update model metadata#

POST/v1/scene/model/update/

Edits the metadata for one or more models. This includes renaming them, or changing their unit interpretation or layout lock.

Each field given (name, units, lock) applies its single value to every model specified by model_ids. Omitting model_ids applies the changes to every model in the scene.

Changing units rescales the model's geometry, so a model can grow past the build volume or into another model. Check with /v1/scene/model/validate/in-bounds/ and /v1/scene/model/validate/interferences/.

Renaming does not touch a model's supports. A raft generated with raft_label_enabled engraves the name the model had at generation time, so once a model is renamed its raft spells a different name until its supports are regenerated. Every model this request renames whose raft carries such a label is reported in warnings as RAFT_LABEL_OUTDATED; call /v1/scene/model/regenerate-supports/ to re-engrave the raft with the current name, or /v1/scene/model/auto-support/ to generate supports afresh.

PreForm does not record the engraved text, so the warning describes this request, not the raft: renaming a model back to the name its raft was generated with still warns, and a rename to the model's current name does not. It is also only raised where a label exists: supports without a raft, mini rafts on the build platform (MINI_RAFTS_ON_BP) and legacy V01 rafts carry none, whatever the knob says.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id is missing, name/units/lock/model_ids has the wrong type, units or lock is outside its enum, a pose field (scale/position/orientation) is present, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene. No models were modified.

Request bodyUpdateModelRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to update. Omitted or empty means every model in the scene.

  • namestring

    Display name to apply. Applying to more than one model at once gives all of them this same name.

  • The measurement units a model was defined in.

    Currently one of the following; more may be added:

    MILLIMETERS
    Model coordinates are millimeters.
    INCHES
    Model coordinates are inches.
  • Orientation and position constraints on a model, respected by automatic layout and packing operations.

    Defaults to "FREE"

    Currently one of the following; more may be added:

    FREE
    Layout operations may move and rotate the model freely.
    LOCKED_XY_FLIP_ENABLED
    Layout may move the model and spin it about the vertical (Z) axis, and may also flip it over; it may not tilt it otherwise.
    LOCKED_XY_ROTATION_FREE_TRANSLATION
    Layout may move the model and spin it about the vertical (Z) axis, but not tilt it.
    LOCKED_ROTATION_FREE_TRANSLATION
    Layout may move the model but not rotate it at all.
    FULLY_LOCKED
    Layout leaves the model exactly where it is.

ReturnsUpdateModelResult

  • modified_model_idsarray of string (uuid)sRequired

    The selected models the update was applied to.

  • warningsarray of UpdateModelWarningsRequired
    Show child attributes
    • codestringRequired

      Currently one of the following; more may be added:

      RAFT_LABEL_OUTDATED
      This request renamed the model, and its raft was generated with raft_label_enabled, so the engraved name no longer matches the rename. Regenerate its supports to update the label.
    • model_idstring (uuid)Required

      The model the warning is about.

The models were updated. Reports which models were updated, and any warnings about side effects the update did not perform.

POST/v1/scene/model/update/
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.update_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        name="bracket-final",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/update/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model_ids": [
      "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    ],
    "name": "bracket-final"
  }'
Python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()
    result = preform.update_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        lock="FULLY_LOCKED",
    )
Terminal
curl -X POST "http://localhost:44388/v1/scene/model/update/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "lock": "FULLY_LOCKED"
  }'
Response
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ],
  "warnings": []
}
JSON
{
  "modified_model_ids": [
    "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
  ],
  "warnings": [
    {
      "code": "RAFT_LABEL_OUTDATED",
      "model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
    }
  ]
}

Delete models#

DELETE/v1/scene/model/delete/

Removes one or more models from a scene. Models are validated before any are deleted: if any requested model does not exist, nothing is deleted.

Omitting model_ids (or sending an empty array) deletes every model in the scene.

Error codes#

HTTPerror.codeCause
400JSON_PARSE_ERRORThe request body is not a valid JSON object.
400JSON_SCHEMA_MATCH_ERRORscene_id is missing, model_ids is present but not an array of strings, or an unrecognized field is present.
400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
404NOT_FOUNDA model in model_ids does not exist in the scene. Nothing was deleted.

Request bodyDeleteModelRequest

  • scene_idstring (uuid)Required

    The id of the scene containing the models.

  • model_idsarray of string (uuid)s

    Ids of the models to delete. Omitted or empty means every model in the scene.

Returnsno body

    The models were deleted. The response has no body.

    DELETE/v1/scene/model/delete/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        preform.delete_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10", "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"],
        )
    Terminal
    curl -X DELETE "http://localhost:44388/v1/scene/model/delete/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
        ]
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        preform.delete_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        )
    Terminal
    curl -X DELETE "http://localhost:44388/v1/scene/model/delete/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }'
    Response
    No response body.

    Replace a model with another model already in the scene#

    POST/v1/scene/model/replace/

    Swaps one model in the scene for another. The model identified by replacement_model_id takes over the pose (position, orientation, scale, units) of the model identified by model_id, and model_id is removed from the scene. The replacement keeps its own id and its own layout lock; set the lock afterwards with /v1/scene/model/update/ if it should match.

    If the replaced model had supports, PreForm attempts to recompute them for the replacement's geometry using the replaced model's support settings and contact locations. Any supports the replacement already carried are discarded. warnings in the response reports when the carry-over could not be done completely; the model may then need supports regenerated with /v1/scene/model/auto-support/.

    Taking over the replaced model's pose, and any recomputed supports, can leave the replacement outside the build volume or overlapping another model. Check with /v1/scene/model/validate/in-bounds/ and /v1/scene/model/validate/interferences/.

    If the replaced model had a user-provided name and the replacement's name was generated from its file, the replacement takes the replaced model's name.

    This endpoint never reads a file. To emulate PreForm's desktop "Replace Model" action, which loads a new version of a part from disk, chain two calls: /v1/scene/model/import/ the new file into the same scene, then call this endpoint with model_id set to the old model and replacement_model_id set to the id import returned. The import places the model with the scene's default snapping; the replacement then moves it onto the old model's pose, so where the import put it does not matter. If the replacement fails, the imported model remains in the scene and can be deleted with /v1/scene/model/delete/. Because the pose carries units, a file imported with default units ends up interpreted in the replaced model's units, exactly as the desktop action does.

    Support regeneration can take a while, so prefer ?async=true and poll the returned operation. Neither model is changed until the whole replacement has succeeded.

    Error codes#

    HTTPerror.codeCause
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORThe body doesn't match the request schema: scene_id, model_id or replacement_model_id is missing or not a string, or an unrecognized field is present.
    400INPUT_ERRORmodel_id or replacement_model_id is not a well-formed UUID, the two ids are equal, scene_id is not a well-formed UUID, or no scene with that id exists. Ids are checked in that order, before either model is looked up.
    404NOT_FOUNDmodel_id or replacement_model_id is well-formed but does not exist in the scene; the message names the missing id. The scene is not changed.
    400OPERATION_FAILEDThe replacement could not be applied: on the replaced model's pose the replacement's geometry would lie outside the build volume or be thinner than one layer, or support regeneration failed outright. The scene is not changed.
    500UNKNOWN_FAILUREAn unexpected exception occurred inside PreFormServer.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyReplaceModelRequest

    • scene_idstring (uuid)Required

      The id of the scene containing both models.

    • model_idstring (uuid)Required

      The id of the model to replace. It is removed from the scene.

    • replacement_model_idstring (uuid)Required

      The id of the model that takes over model_id's pose and supports. It keeps this id and its own layout lock. Must differ from model_id.

    Returns 200 OKReplaceModelResult

    • warningsarray of enum ReplaceWarningsRequired

      Currently one of the following; more may be added:

      NOT_ALL_SUPPORTS_COMPUTED_SUCCESSFULLY
      Support generation for the new geometry did not finish. The new model may have no supports.
      NOT_ALL_CONTACT_LOCATIONS_APPLIED
      Supports were generated, but not every contact location from the original model's supports could be carried over to the new geometry.

    The model was replaced: the replacement now has the replaced model's pose, and model_id is gone from the scene. warnings reports any problem carrying the supports over.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    The replacement was accepted for asynchronous processing (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/replace/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.replace_model(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_id="c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
            replacement_model_id="a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/replace/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
        "replacement_model_id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
      }'
    Response
    JSON
    {
      "warnings": []
    }
    JSON
    {
      "warnings": [
        "NOT_ALL_CONTACT_LOCATIONS_APPLIED"
      ]
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Duplicate models#

    POST/v1/scene/model/duplicate/

    Creates count copies of each selected model, placed alongside the originals. Models are validated before any are duplicated: if any requested model does not exist, nothing is duplicated.

    Returns one results entry per selected model, in request order, naming the ids of the copies created from it (in no particular order).

    Only models with their own geometry can be duplicated: selecting a group or cage model fails with INPUT_ERROR, and nothing is duplicated.

    count is capped at 1000 per selected model to bound how much a single request can add to a scene.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id or count is missing, or count is not an integer or is outside 1-1000. Also returned when model_ids is present but not an array of strings, or when an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists. Also returned when a model in model_ids is a group or cage with no geometry of its own. Nothing was duplicated.
    404NOT_FOUNDA model in model_ids does not exist in the scene. Nothing was duplicated.

    Request bodyDuplicateModelRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to duplicate. Omitted or empty means every model in the scene.

    • countintegerRequired

      The number of copies to create of each selected model.

      At least 1, at most 1000.

    ReturnsDuplicateModelsResult

    • resultsarray of ModelsCreatedFromSourcesRequired

      One entry per selected model, in request order. Each entry's new_model_ids are the copies created from that model. The source models themselves are unchanged.

      Show child attributes
      • source_model_idstring (uuid)Required

        The id of the model the new models were created from.

      • new_model_idsarray of string (uuid)sRequired

        Ids of the models created from it, in no particular order.

    The models were duplicated. results has one entry per selected model, in request order.

    POST/v1/scene/model/duplicate/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.duplicate_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            count=2,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/duplicate/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "count": 2
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.duplicate_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            count=1,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/duplicate/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "count": 1
      }'
    Response
    JSON
    {
      "results": [
        {
          "source_model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "new_model_ids": [
            "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
            "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
          ]
        }
      ]
    }

    Cage models#

    POST/v1/scene/model/cage/

    Generates a support cage around the selected models: a bar-lattice frame that encloses them so they can be handled and depowdered as one rigid unit, protecting small or fragile parts from being lost or damaged. Any existing cage or group among the selected models is replaced.

    Fields left unspecified fall back to the scene's default cage-generation settings. enable_square_bars: true (the default) gives the bars a rectangular cross-section (bar_width_mm by bar_thickness_mm). false gives them a round cross-section.

    Caging is an SLS-only feature. Caging a scene on an unsupported (SLA) machine fails with OPERATION_FAILED.

    Unlike the other model-modifying endpoints, this one does not support ?async=true.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id is missing, model_ids is present but not an array of strings, a bar/distance field is below its minimum, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    404NOT_FOUNDA model in model_ids does not exist in the scene. Nothing is caged.
    400OPERATION_FAILEDThe scene's machine type does not support part caging, not all models fit within the build volume once the cage is added, or cage generation produced no cage model. The message carries the underlying reason.

    Request bodyCageRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to cage. Omitted or empty means every model in the scene.

    • cage_labelstring

      Display name for the new cage model. Defaults to an auto-generated name ("Cage 1", "Cage 2", ...).

    • generate_mesh_labelboolean

      Emboss cage_label as a physical label on the cage geometry.

      Defaults to true

    • bar_spacing_mmnumber

      Gap left between adjacent cage bars.

      At least 0.

    • bar_thickness_mmnumber

      Thickness of each cage bar.

      At least 1.

    • bar_width_mmnumber

      Width of each cage bar.

      At least 1.

    • distance_to_cage_mmnumber

      Gap left between the models and the surrounding cage.

      At least 0.

    • enable_round_edgesboolean

      Round off the edges of the cage bars.

      Defaults to false

    • enable_square_barsboolean

      Give the cage bars a rectangular cross-section rather than a round one.

      Defaults to true

    ReturnsCageResult

    • cage_model_idstring (uuid)Required

      The id of the newly created cage model. The selected models are now children of this model.

    The cage was created. cage_model_id is the id of the new cage model (is_cage: true, is_group: true in /v1/scene/model/list/). The selected models are now its children.

    POST/v1/scene/model/cage/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.cage_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10", "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/cage/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
        ]
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.cage_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            cage_label="Cage - batch 12",
            bar_spacing_mm=8,
            bar_thickness_mm=2,
            bar_width_mm=2,
            distance_to_cage_mm=3,
            enable_round_edges=True,
            enable_square_bars=False,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/cage/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "cage_label": "Cage - batch 12",
        "bar_spacing_mm": 8,
        "bar_thickness_mm": 2,
        "bar_width_mm": 2,
        "distance_to_cage_mm": 3,
        "enable_round_edges": true,
        "enable_square_bars": false
      }'
    Response
    JSON
    {
      "cage_model_id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
    }

    Label models#

    POST/v1/scene/model/label/

    Embosses or engraves a text label onto the surface of one or more models, at the given position and orientation. position and orientation are absolute build-platform coordinates, and the identical point and orientation are reused for every id in model_ids.

    Every model in model_ids must exist in the scene, and the label must apply cleanly to each one at the selected point. Otherwise the request fails and nothing is labeled.

    depth_mm is the height the label rises above the surface (application_mode: "EMBOSS", the default) or the depth it is cut into the surface (application_mode: "ENGRAVE").

    Labeling can fail for individual characters (e.g. an unsupported glyph) without failing the whole request. That is reported as a warnings entry rather than an error. If every character fails, the request itself fails with OPERATION_FAILED.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id, orientation, position, label, font_size_mm, or depth_mm is missing or malformed. Also returned when font_size_mm/depth_mm is outside its allowed range, application_mode is outside its enum, model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400OPERATION_FAILEDA model in model_ids does not exist in the scene (nothing is labeled), or label generation itself failed, e.g. the shared position/orientation don't land on one of the selected models' surfaces. The message carries the underlying reason.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyLabelRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to label. Omitted or empty means every model in the scene.

    • orientationEulerAngles or TransformMatrix or DirectionVectorsRequired

      A model orientation, given as exactly one of: Euler angles, a transform matrix, or a pair of direction vectors.

      Show child attributes
      • EulerAnglesoption 1 of 3

        Orientation as Euler angles in degrees. Rotations are applied in the order z, then x, then y.

        Show fields
        • xnumberRequired

          Rotation in degrees around the x axis (applied second).

        • ynumberRequired

          Rotation in degrees around the y axis (applied last).

        • znumberRequired

          Rotation in degrees around the z axis (applied first).

      • TransformMatrixoption 2 of 3

        Orientation as the linear (rotation/scale) part of a 3x3 transform matrix.

        Show fields
        • lineararray of array of numbersRequired

          A 3x3 matrix, given as 3 rows of 3 numbers.

          Exactly 3 items.

      • DirectionVectorsoption 3 of 3

        Orientation as two vectors, in model space, that say which way is "up" and "right" once the model is placed in the scene.

        Show fields
        • z_directionarray of numbersRequired

          Unit vector in model space that will point up (+Z) in the scene.

          Exactly 3 items.

        • x_directionarray of numbersRequired

          Unit vector in model space, perpendicular to z_direction, that will point right (+X) in the scene.

          Exactly 3 items.

    • positionScenePositionRequired

      A position within a printer's build volume, in millimeters, in user coordinate space.

      Show child attributes
      • xnumberRequired

        X position. 0 is the center of the build volume. Positive moves right as you face the printer.

      • ynumberRequired

        Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

      • znumberRequired

        Vertical position. 0 is the surface of the build platform.

    • labelstringRequired

      The text to render.

    • How a label is applied to a model's surface.

      Defaults to "EMBOSS"

      One of:

      EMBOSS
      Raise the label above the surface by depth_mm.
      ENGRAVE
      Cut the label into the surface to a depth of depth_mm.
    • font_size_mmnumberRequired

      Label character height.

      At least 2, at most 20.

    • depth_mmnumberRequired

      Height above the surface (EMBOSS) or depth into it (ENGRAVE).

      At least 0.2, at most 10.

    Returns 200 OKLabelResult

    • warningsarray of stringsRequired

      Free-text, non-fatal problems encountered while applying the label (e.g. individual characters that could not be created).

    • infosarray of stringsRequired

      Free-text informational notices, if any.

    The label was applied to every selected model. warnings is non-empty if some characters of the label could not be created.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    Labeling started asynchronously (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/label/
    Python
    from formlabs_sdk import FormlabsClient
    from formlabs_preform_api.models import EulerAngles, ScenePosition
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.label_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            label="Form 4",
            orientation=EulerAngles(x=0, y=0, z=0),
            position=ScenePosition(x=0, y=0, z=5),
            font_size_mm=5,
            depth_mm=0.5,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/label/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "label": "Form 4",
        "orientation": {
          "x": 0,
          "y": 0,
          "z": 0
        },
        "position": {
          "x": 0,
          "y": 0,
          "z": 5
        },
        "font_size_mm": 5,
        "depth_mm": 0.5
      }'
    Python
    from formlabs_sdk import FormlabsClient
    from formlabs_preform_api.models import EulerAngles, LabelApplicationMode, ScenePosition
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.label_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            label="SN-0042",
            application_mode=LabelApplicationMode("ENGRAVE"),
            orientation=EulerAngles(x=0, y=0, z=0),
            position=ScenePosition(x=0, y=0, z=5),
            font_size_mm=4,
            depth_mm=0.3,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/label/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "label": "SN-0042",
        "application_mode": "ENGRAVE",
        "orientation": {
          "x": 0,
          "y": 0,
          "z": 0
        },
        "position": {
          "x": 0,
          "y": 0,
          "z": 5
        },
        "font_size_mm": 4,
        "depth_mm": 0.3
      }'
    Response
    JSON
    {
      "warnings": [],
      "infos": []
    }
    JSON
    {
      "warnings": [
        "Partial failure: 2 characters failed to be created"
      ],
      "infos": []
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Drill holes#

    POST/v1/scene/model/drill/

    Drills one or more drain/vent holes into the selected models at the given positions and orientations, so trapped resin can escape during printing. Set create_plug: true on a hole to also generate a matching plug model that can be reinserted after printing. Each created plug is reported in plugs with its own id and the id of the drilled model it was cut from.

    The same set of drain_holes is applied to every selected model. Every model must exist in the scene, or the request fails and nothing is drilled.

    Each hole's position and orientation are absolute build-platform coordinates, not positions relative to each model: the identical point and axis are reused for every id in model_ids. Selecting several models only drills a sensible hole on each of them when they actually share that location (e.g. exact duplicates stacked in place). Otherwise the point will miss the surface of most of the selected models.

    Problems with an individual hole (the given point is not on the model's surface, the hole is too large for the model, or a requested plug would be too small to print) do not fail the request. They are reported in warnings, identified by the hole's index into drain_holes, and that hole is skipped for that model. This makes an off-surface point far less disruptive here than for /v1/scene/model/label/: the other holes and models still proceed. If nothing was accomplished (every hole was skipped on every selected model and no plug was created), the request fails with OPERATION_FAILED instead, and the scene is left unmodified.

    Set depth_mm to "AUTO" to infer the hole depth by searching into the model along the hole's axis. max_search_distance bounds how far that search goes, and is only accepted together with depth_mm: "AUTO".

    Drilling does not work on non-manifold models, but does not verify its input either, because that check is not free. If there is any doubt about a model's integrity, check it with /v1/scene/model/validate/manifoldness/ first and repair it with /v1/scene/model/repair/ if it is not manifold. Failure reports repeat this suggestion.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id is missing, drain_holes is missing or empty, an entry is missing a required field or has the wrong type, an entry sets max_search_distance without depth_mm: "AUTO", model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID or no scene with that id exists, or a model in model_ids does not exist in the scene. Nothing is drilled.
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400OPERATION_FAILEDNothing was accomplished: every hole was skipped on every selected model and no plug was created. The message carries the per-hole reasons and suggests the manifoldness-validation endpoint. The scene was not modified.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyDrillRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to drill. Omitted or empty means every model in the scene.

    • drain_holesarray of DrainHolesRequired

      At least 1 items.

      Show child attributes
      • orientationEulerAngles or TransformMatrix or DirectionVectorsRequired

        A model orientation, given as exactly one of: Euler angles, a transform matrix, or a pair of direction vectors.

        Show child attributes
        • EulerAnglesoption 1 of 3

          Orientation as Euler angles in degrees. Rotations are applied in the order z, then x, then y.

          Show fields
          • xnumberRequired

            Rotation in degrees around the x axis (applied second).

          • ynumberRequired

            Rotation in degrees around the y axis (applied last).

          • znumberRequired

            Rotation in degrees around the z axis (applied first).

        • TransformMatrixoption 2 of 3

          Orientation as the linear (rotation/scale) part of a 3x3 transform matrix.

          Show fields
          • lineararray of array of numbersRequired

            A 3x3 matrix, given as 3 rows of 3 numbers.

            Exactly 3 items.

        • DirectionVectorsoption 3 of 3

          Orientation as two vectors, in model space, that say which way is "up" and "right" once the model is placed in the scene.

          Show fields
          • z_directionarray of numbersRequired

            Unit vector in model space that will point up (+Z) in the scene.

            Exactly 3 items.

          • x_directionarray of numbersRequired

            Unit vector in model space, perpendicular to z_direction, that will point right (+X) in the scene.

            Exactly 3 items.

      • positionScenePositionRequired

        A position within a printer's build volume, in millimeters, in user coordinate space.

        Show child attributes
        • xnumberRequired

          X position. 0 is the center of the build volume. Positive moves right as you face the printer.

        • ynumberRequired

          Y position. 0 is the center of the build volume. Positive moves away from you as you face the printer.

        • znumberRequired

          Vertical position. 0 is the surface of the build platform.

      • diameter_mmnumberRequired

        Diameter of the drilled hole.

        Greater than 0.

      • depth_mmnumber or stringRequired

        Fixed hole depth, or the literal string "AUTO" to infer the depth by searching into the model along the hole's axis.

      • outset_mmnumber

        How far the hole extends outward past the model's surface (the reverse of the drilling direction), to cleanly cut the surface edge.

        At least 0.

        Defaults to 1

      • create_plugboolean

        Also generate a removable plug model matching this hole.

        Defaults to false

      • max_search_distancenumber

        Upper bound on the depth search. Only accepted together with depth_mm: "AUTO".

        At least 0.

    Returns 200 OKDrillResult

    • plugsarray of PlugCreatedsRequired

      One entry per plug model created for holes with create_plug: true. Empty if no plugs were created.

      Show child attributes
      • plug_model_idstring (uuid)Required

        The id of the newly created plug model.

      • drilled_model_idstring (uuid)Required

        The id of the pre-existing model that was drilled: the model the plug was cut from and fits back into.

    • warningsarray of stringsRequired

      Free-text, non-fatal problems drilling individual holes, identified by their index into the request's drain_holes.

    • infosarray of stringsRequired

      Free-text informational notices, if any.

    The drain holes were processed. Always 200, even when some or all requested holes could not be drilled. Check warnings.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    Drilling started asynchronously (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/drill/
    Python
    from formlabs_sdk import FormlabsClient
    from formlabs_preform_api.models import DrainHole
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.drill_holes(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            drain_holes=[
                DrainHole.from_dict({
                    "orientation": {
                        "x": 0,
                        "y": 0,
                        "z": 0,
                    },
                    "position": {
                        "x": 0,
                        "y": 0,
                        "z": 0,
                    },
                    "diameter_mm": 3,
                    "depth_mm": "AUTO",
                    "outset_mm": 1,
                }),
            ],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/drill/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "drain_holes": [
          {
            "orientation": {
              "x": 0,
              "y": 0,
              "z": 0
            },
            "position": {
              "x": 0,
              "y": 0,
              "z": 0
            },
            "diameter_mm": 3,
            "depth_mm": "AUTO",
            "outset_mm": 1
          }
        ]
      }'
    Python
    from formlabs_sdk import FormlabsClient
    from formlabs_preform_api.models import DrainHole
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.drill_holes(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            drain_holes=[
                DrainHole.from_dict({
                    "orientation": {
                        "x": 0,
                        "y": 0,
                        "z": 1,
                    },
                    "position": {
                        "x": 2.67,
                        "y": 0,
                        "z": 0.33,
                    },
                    "diameter_mm": 3,
                    "depth_mm": 4,
                    "outset_mm": 1,
                    "create_plug": True,
                }),
            ],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/drill/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "drain_holes": [
          {
            "orientation": {
              "x": 0,
              "y": 0,
              "z": 1
            },
            "position": {
              "x": 2.67,
              "y": 0,
              "z": 0.33
            },
            "diameter_mm": 3,
            "depth_mm": 4,
            "outset_mm": 1,
            "create_plug": true
          }
        ]
      }'
    Response
    JSON
    {
      "plugs": [],
      "warnings": [],
      "infos": []
    }
    JSON
    {
      "plugs": [
        {
          "plug_model_id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
          "drilled_model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        }
      ],
      "warnings": [],
      "infos": []
    }
    JSON
    {
      "plugs": [],
      "warnings": [
        "Drain hole is too large for drain hole [0]"
      ],
      "infos": []
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Hollow models#

    POST/v1/scene/model/hollow/

    Hollows the selected models, replacing each one's interior with a cavity that leaves a shell of wall_thickness_mm behind. If a model already has a hollow applied, this replaces it with the new parameters rather than hollowing the already-hollow shell again.

    feature_size_mm is the smallest feature size the generated inner surface will resolve. Smaller values increase quality at the cost of processing time. Values above 1 are clamped to 1.

    accuracy controls how closely hollowing follows the model's true offset surface, with speed as the tradeoff: 1 is the most accurate and slowest, 0 the fastest and coarsest. The default is 0.5.

    Hollowing can fail for an individual model without failing the whole request. This is most commonly because wall_thickness_mm is too large for its geometry. That model is left unhollowed, listed in unhollowed_model_ids and explained in warnings, while every other selected model still proceeds. If no selected model could be hollowed, the request fails with OPERATION_FAILED instead, and the scene is left unmodified.

    Hollowing does not work on non-manifold models, but does not verify its input either, because that check is not free. If there is any doubt about a model's integrity, check it with /v1/scene/model/validate/manifoldness/ first and repair it with /v1/scene/model/repair/ if it is not manifold. Failure reports repeat this suggestion.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id is missing, wall_thickness_mm is missing or below its minimum, feature_size_mm or accuracy is outside its allowed range, model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID or no scene with that id exists, or a model in model_ids does not exist in the scene. Nothing is hollowed.
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400OPERATION_FAILEDNo selected model could be hollowed. The message carries the per-model reasons and suggests the manifoldness-validation endpoint. The scene was not modified.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyHollowRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to hollow. Omitted or empty means every model in the scene.

    • wall_thickness_mmnumberRequired

      Thickness of the shell wall left after hollowing.

      At least 0.1.

    • feature_size_mmnumber

      Smallest feature size the generated inner surface will resolve. Smaller is higher quality and slower. Values above 1 are silently clamped to 1.

      At least 0.05.

      Defaults to 1

    • accuracynumber

      How closely the inner surface follows the model's true offset surface: 1 is highest quality and slowest, 0 is fastest and coarsest.

      At least 0, at most 1.

      Defaults to 0.5

    Returns 200 OKHollowResult

    • hollowed_model_idsarray of string (uuid)sRequired

      Selected models that were hollowed. Never empty.

    • unhollowed_model_idsarray of string (uuid)sRequired

      Selected models that could not be hollowed and were left unchanged. Each one also has a line in warnings.

    • warningsarray of stringsRequired

      Free-text, non-fatal problems hollowing individual models (e.g. wall_thickness_mm too large for a model's geometry), one line per model in unhollowed_model_ids, followed by a suggestion to check manifoldness when any model failed.

    • infosarray of stringsRequired

      Free-text informational notices, if any.

    At least one selected model was hollowed. Reports which selected models were hollowed and which were left unhollowed.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    Hollowing started asynchronously (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/hollow/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.hollow_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            wall_thickness_mm=1.5,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/hollow/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "wall_thickness_mm": 1.5
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.hollow_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            wall_thickness_mm=1.5,
            feature_size_mm=0.5,
            accuracy=0.8,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/hollow/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "wall_thickness_mm": 1.5,
        "feature_size_mm": 0.5,
        "accuracy": 0.8
      }'
    Response
    JSON
    {
      "hollowed_model_ids": [
        "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
      ],
      "unhollowed_model_ids": [],
      "warnings": [],
      "infos": []
    }
    JSON
    {
      "hollowed_model_ids": [
        "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
      ],
      "unhollowed_model_ids": [
        "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
      ],
      "warnings": [
        "Failed to hollow model with ID: c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
        "If a model may not be manifold, you can check it with /v1/scene/model/validate/manifoldness/ and repair it with /v1/scene/model/repair/."
      ],
      "infos": []
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Dental-hollow models#

    POST/v1/scene/model/dental-hollow/

    Cuts each selected model at a plane, extrudes a solid base of extrude_distance_mm downward from the cut to make it printable, and replaces the original model in the scene with the result. Set hollow: true to also hollow that result using dental-specific parameters (drain holes, honeycomb infill) rather than /v1/scene/model/hollow/'s.

    This is the API counterpart to Desktop PreForm's "Scan to Model" tool in the Dental Workspace sidebar: it uses similar orient/trim/extrude/hollow processing, but runs it on models already in the scene and replaces its manual cut-plane placement with automatic gumline detection.

    Designed and tested for dental arch models (e.g. intraoral or model scans of a full or partial arch). Behavior on other model types is unsupported.

    The cut plane is either an absolute height (cutoff_height_mm) or an offset from each model's automatically detected gumline (cutoff_below_gumline_mm, default 0, exactly at the gumline). These two are mutually exclusive.

    shell_thickness_mm, wall_thickness_mm, drain_hole_radius_mm, drain_hole_height_ratio, drain_hole_suppression_distance_mm, drain_hole_max_count, and enable_honeycomb_infill only take effect when hollow: true. Supplying any of them while hollow is false (the default) is rejected. wall_thickness_mm here is the thickness of the honeycomb infill walls inside the hollow cavity, not the outer shell, which is shell_thickness_mm.

    Every model in model_ids must exist in the scene, or the request fails and nothing is changed. The response returns one results entry per selected model, in request order, naming the id of the processed model created from it. The source models themselves no longer exist in the scene.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id or extrude_distance_mm is missing, cutoff_height_mm and cutoff_below_gumline_mm are both given, hollow is false (the default) while any hollow-only field (shell_thickness_mm, wall_thickness_mm, drain_hole_radius_mm, drain_hole_height_ratio, drain_hole_suppression_distance_mm, drain_hole_max_count, enable_honeycomb_infill) is given, a field has the wrong type, a numeric field is outside its allowed range, model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400OPERATION_FAILEDA model in model_ids does not exist in the scene, or the cut/extrude/hollow processing itself failed. Nothing is changed.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyDentalHollowingRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to process. Omitted or empty means every model in the scene.

    • cutoff_height_mmnumber

      Absolute height of the cut plane. Mutually exclusive with cutoff_below_gumline_mm.

    • cutoff_below_gumline_mmnumber

      Offset of the cut plane from each model's automatically detected gumline. Only used when cutoff_height_mm is omitted. 0 (the default) cuts exactly at the gumline.

      Defaults to 0

    • extrude_distance_mmnumberRequired

      Length of solid material extruded downward from the cut plane to create a printable base.

    • hollowboolean

      Also hollow the resulting model.

      Defaults to false

    • shell_thickness_mmnumber

      Thickness of the hollow shell wall. Defaults to 2.75 if omitted. Only used when hollow is true. Rejected otherwise.

      At least 0.

    • wall_thickness_mmnumber

      Thickness of the honeycomb infill walls inside the hollow cavity (not the outer shell, which is shell_thickness_mm). Defaults to 1 if omitted. Only used when hollow is true. Rejected otherwise.

      At least 0.01.

    • drain_hole_radius_mmnumber

      Minor-axis radius of the drain holes generated in the hollow shell. Defaults to 1.5 if omitted. Only used when hollow is true. Rejected otherwise.

      At least 0.

    • drain_hole_height_rationumber

      Ratio of a drain hole's major axis to drain_hole_radius_mm. 1 (the default) produces a circular hole and larger values elongate it. Only used when hollow is true. Rejected otherwise.

      Greater than 0.

    • drain_hole_suppression_distance_mmnumber

      Minimum distance between generated drain holes; candidates closer than this to another hole are dropped. Defaults to 2 if omitted. Only used when hollow is true. Rejected otherwise.

      At least 0.

    • drain_hole_max_countinteger

      Maximum number of drain holes generated per contour on the bottom of the model. Defaults to 2 if omitted. Only used when hollow is true. Rejected otherwise.

      At least 0.

    • enable_honeycomb_infillboolean

      Fill the hollow cavity with honeycomb infill. Only used when hollow is true. Rejected otherwise.

      Defaults to true

    • enable_smooth_contour_extended_sidesboolean

      Smooth contours that are extended down to the build platform when creating the base.

      Defaults to true

    • keep_disconnected_mesh_componentsboolean

      Keep every disconnected piece of the cut mesh instead of dropping all but the largest. Defaults to the scene's configured default if omitted.

    Returns 200 OKDentalHollowingResult

    • resultsarray of ModelsCreatedFromSourcesRequired

      One entry per selected model, in request order. Each entry's new_model_ids name the processed model created from that model (exactly one today, though the array form leaves room for processing that yields several). The source model no longer exists in the scene.

      Show child attributes
      • source_model_idstring (uuid)Required

        The id of the model the new models were created from.

      • new_model_idsarray of string (uuid)sRequired

        Ids of the models created from it, in no particular order.

    • warningsarray of stringsRequired

      Free-text, non-fatal problems encountered while processing.

    • infosarray of stringsRequired

      Free-text informational notices, if any.

    The models were processed. results has one entry per selected model, in request order. The source models no longer exist in the scene.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    Processing started asynchronously (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/dental-hollow/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.dental_hollow_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            cutoff_height_mm=10,
            extrude_distance_mm=2.1,
            hollow=False,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/dental-hollow/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "cutoff_height_mm": 10,
        "extrude_distance_mm": 2.1,
        "hollow": false
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.dental_hollow_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
            cutoff_below_gumline_mm=3,
            extrude_distance_mm=2.1,
            hollow=True,
            shell_thickness_mm=2.75,
            drain_hole_suppression_distance_mm=5,
            drain_hole_max_count=10,
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/dental-hollow/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ],
        "cutoff_below_gumline_mm": 3,
        "extrude_distance_mm": 2.1,
        "hollow": true,
        "shell_thickness_mm": 2.75,
        "drain_hole_suppression_distance_mm": 5,
        "drain_hole_max_count": 10
      }'
    Response
    JSON
    {
      "results": [
        {
          "source_model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "new_model_ids": [
            "b7e2b0f4-9a2d-4b3d-8f4a-2e7c1a9d10c2"
          ]
        }
      ],
      "warnings": [],
      "infos": []
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Repair models#

    POST/v1/scene/model/repair/

    Attempt to repair the selected models' meshes: stitch open boundaries, split non-manifold vertices, remove degenerate faces, and fill holes. Each repaired model keeps its id, position, orientation, and metadata. Only its mesh is replaced.

    Repair is best-effort: a 200 response means the repair pipeline ran to completion for every selected model, and each model's entry reports the manifold library's verdict on the repaired mesh, the same check as /v1/scene/model/validate/manifoldness/. A model whose entry says is_manifold: false still carries residual defects.

    The scene is committed atomically: if repairing any selected model fails, no model's mesh is replaced.

    Repair is a slow operation: its worst-case runtime is O(n²) in the number of triangles in the mesh. Prefer ?async=true and poll the returned operation. /v1/scene/model/validate/manifoldness/ is much cheaper (though still not free). When you only suspect a model may be broken, check first and repair only the models that are not manifold.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORThe body does not match the request schema: scene_id missing, a field has the wrong type, model_ids is present but not an array of strings, or an unknown field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    400INVALID_QUERY_PARAMETERasync is present but not true or false.
    400OPERATION_FAILEDThe repair itself failed for one of the models, or mesh repair is not available in this build. The message carries the underlying reason. The scene was not modified.
    404NOT_FOUNDA model in model_ids does not exist in the scene. The scene was not modified.

    Query parameters

    • asyncboolean

      When true, returns 202 Accepted immediately with the operation's id and its operation_url to poll for the result, instead of blocking until the operation completes.

      Defaults to false

    Request bodyRepairModelsRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to repair. Omitted or empty means every model in the scene.

    Returns 200 OKobject

    • per_model_resultsmap of ModelRepairResultRequired

      Map of model id to that model's repair result.

      Show child attributes
      • is_manifoldbooleanRequired

        The manifold library's verdict on the repaired mesh, equivalent to a /v1/scene/model/validate/manifoldness/ status of MANIFOLD. false means the best-effort repair left residual defects in the mesh (it was still replaced in the scene).

    Every selected model was repaired and the scene was updated. One entry per selected model, keyed by model id, reporting the manifold library's verdict on the repaired mesh.

    Returns 202 AcceptedOperationAccepted

    • operation_idstring (uuid)Required

      Randomly generated id of the tracked operation.

    • operation_urlstringRequired

      Server-relative URL of this operation's record: /v1/operation/get/?operation_id=<operation_id>. GET it to follow progress and retrieve the result.

    • messagestringRequired

      Human-readable pointer to where the result will appear. Purely informational. Use operation_url programmatically.

    The repair started asynchronously (?async=true). Poll the operations endpoints with the returned id.

    Response headers (202)

    • Locationstring

      Server-relative URL of the accepted operation's record, identical to the response body's operation_url. GET it to follow progress and retrieve the result.

    POST/v1/scene/model/repair/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.repair_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/repair/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.repair_models(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/repair/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10"
        ]
      }'
    Response
    JSON
    {
      "per_model_results": {
        "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10": {
          "is_manifold": true
        },
        "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55": {
          "is_manifold": false
        }
      }
    }
    JSON
    {
      "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7 until 'status' is SUCCEEDED or FAILED; the endpoint's response will appear in the 'result' field there."
    }

    Split models into separate bodies#

    POST/v1/scene/model/split-bodies/

    Splits each selected model into one new model per disconnected body in its mesh, removing the original. Each new model is named "<original name> (Body N)" and keeps the original's position and orientation, subject to the printer's default build-platform snap settings (SLA printers snap each new body to the platform in Z, while SLS does not). Supports are not carried over to the new models.

    Returns one results entry per selected model, in request order, naming the ids of the bodies split out of it (in no particular order. Each body's name carries its (Body N) index). The source models themselves no longer exist in the scene.

    Models are validated before any are split: if any requested model does not exist, nothing is split. Splitting is also all-or-nothing across the whole request: if any selected model has only one connected body (or no valid geometry) and cannot be split, no model in the request is split.

    Use /v1/scene/model/body-count/ first to find which models have more than one body.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id is missing, model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    404NOT_FOUNDA model in model_ids does not exist in the scene. Nothing is split.
    400OPERATION_FAILEDA model in model_ids has only one connected body (or no valid geometry) and cannot be split. Nothing is split.

    Request bodySplitModelBodiesRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to split. Omitted or empty means every model in the scene.

    ReturnsSplitModelBodiesResult

    • resultsarray of ModelsCreatedFromSourcesRequired

      One entry per selected model, in request order. Each entry's new_model_ids are the per-body models split out of that model, in no particular order (each body's name carries its "(Body N)" index). The source model no longer exists in the scene.

      Show child attributes
      • source_model_idstring (uuid)Required

        The id of the model the new models were created from.

      • new_model_idsarray of string (uuid)sRequired

        Ids of the models created from it, in no particular order.

    The selected models were split. results has one entry per selected model, in request order. The source models no longer exist in the scene.

    POST/v1/scene/model/split-bodies/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.split_model_bodies(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10", "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/split-bodies/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
        ]
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.split_model_bodies(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/split-bodies/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }'
    Response
    JSON
    {
      "results": [
        {
          "source_model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "new_model_ids": [
            "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
            "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55"
          ]
        }
      ]
    }

    Get model body counts#

    POST/v1/scene/model/body-count/

    Returns the number of disconnected bodies in each selected model's mesh. A body_count greater than 1 means the model consists of several disconnected shells, which is often unintentional, e.g. several parts imported together as one file. Use /v1/scene/model/split-bodies/ to split such a model into one model per body.

    Error codes#

    HTTPerror.codeCause
    400JSON_PARSE_ERRORThe request body is not a valid JSON object.
    400JSON_SCHEMA_MATCH_ERRORscene_id is missing, model_ids is present but not an array of strings, or an unrecognized field is present.
    400INPUT_ERRORscene_id is not a well-formed UUID, or no scene with that id exists.
    404NOT_FOUNDA model in model_ids does not exist in the scene.

    Request bodyGetModelBodyCountRequest

    • scene_idstring (uuid)Required

      The id of the scene containing the models.

    • model_idsarray of string (uuid)s

      Ids of the models to inspect. Omitted or empty means every model in the scene.

    Returnsobject

    • per_model_resultsmap of BodyCountResultRequired

      Map of model id to that model's body count.

      Show child attributes
      • body_countintegerRequired

        Number of disconnected bodies (connected mesh components) making up this model. 1 for an ordinary single-body model.

    One entry per requested model, keyed by model id. If model_ids is omitted or empty, every model in the scene is counted.

    POST/v1/scene/model/body-count/
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.get_model_body_counts(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
            model_ids=["c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10", "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"],
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/body-count/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "model_ids": [
          "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10",
          "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
        ]
      }'
    Python
    from formlabs_sdk import FormlabsClient
    
    with FormlabsClient() as client:
        preform = client.preform.connect()
        result = preform.get_model_body_counts(
            scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        )
    Terminal
    curl -X POST "http://localhost:44388/v1/scene/model/body-count/" \
      -H "Content-Type: application/json" \
      -d '{
        "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }'
    Response
    JSON
    {
      "per_model_results": {
        "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10": {
          "body_count": 2
        },
        "9b1dd8a0-4f6e-45c2-9a3e-77a01f2b6c55": {
          "body_count": 1
        }
      }
    }
    Feedback & Bug Reports