Skip to content

Automation API

ObjectiveFrame can be driven from another program over a small HTTP service. It is the same set of commands the user interface and the scripts use, so a Python program can build a model, solve it, and read results back while the application stays on screen and keeps drawing.

This is what the Python client in python/rest_client/ofapi.py talks to.

Starting the service

The service is off by default. Start it from either:

  • the start page, with Start automation API, or
  • File / Preferences..., with the Start service button.

It then listens on http://localhost:8081.

Warning

The service has no authentication and is intended for a local, trusted session. It accepts any request that reaches it. Do not expose port 8081 beyond the machine ObjectiveFrame runs on.

Calling it

Every command is a POST to /cmds/<name>. Arguments go in the request body, as JSON for most commands and as a plain filename string for the file commands.

Commands that only act return 200 OK with an empty text/html body. Queries return application/json:

Shape Returned by
{"value": n} Counts, indices, and boolean queries
{"pos": [x, y, z]} node_pos_at
{"i0": i0, "i1": i1} beam_at
{"min": [...], "max": [...]} model_bounds

Most handlers read their arguments strictly, so a missing field is an error. The three array commands are the exception: only the count is required, and everything else falls back to the same defaults the command itself uses — {"count": 4} is a complete request.

import json, requests

url = "http://localhost:8081"

requests.post(url + "/cmds/new_model")
requests.post(url + "/cmds/add_nodes", json.dumps([[0, 0, 0], [4, 0, 0], [2, 3, 0]]))
requests.post(url + "/cmds/add_beams", json.dumps([[0, 1], [0, 2], [1, 2]]))
requests.post(url + "/cmds/assign_node_fixed_bc_ground")
requests.post(url + "/cmds/add_node_load_at",
              json.dumps({"index": 2, "force": [0.0, -1000.0, 0.0]}))

n = requests.post(url + "/cmds/node_count").json()["value"]
print(n, "nodes")

Commands

Model lifecycle

Endpoint Body
new_model
open_model filename
save_model filename
export_model filename, as CALFEM for Python
import_model filename, from CALFEM for Python
snap_shot — (places an undo point)

Creating nodes and elements

Endpoint Body
add_nodes [[x, y, z], ...]
add_beams [[i0, i1], ...]
mesh_selected_nodes
surface_selected_nodes

Selection

Endpoint Body
select_all
select_all_nodes
clear_selection
add_last_node_to_selection
select_node_at {"index": i}
select_beam_at {"index": i}

Editing nodes and elements

Endpoint Body
delete_node_at {"index": i}
delete_beam_at {"index": i}
subdivide_beam_at {"index": i}
connect_near_nodes {"tolerance": t}
update_node_pos_at {"index": i, "pos": [x, y, z]}
update_beam_at {"index": i, "i0": i0, "i1": i1}

Boundary conditions

Endpoint Body
assign_node_fixed_bc_ground
assign_node_pos_bc_ground
assign_node_fixed_bc_at {"index": i}
assign_node_pos_bc_at {"index": i}
remove_node_bc_at {"index": i}
clear_all_bcs

Loads

Endpoint Body
add_node_load_at {"index": i, "force": [fx, fy, fz]}
clear_node_load_at {"index": i}
add_beam_load_at {"index": i, "force": [fx, fy, fz]}
clear_beam_load_at {"index": i}
clear_all_loads

Self-weight

Endpoint Body
set_self_weight_enabled {"enabled": true}
set_self_weight_mode {"mode": m} — 0 material density, 1 total load, 2 mass per length
set_gravity {"value": g}, with an optional "scale" for the load factor
set_total_weight {"value": w}
set_mass_per_length {"value": m}

Geometry modification

These act on the current selection, so select something first.

Endpoint Body
array_selection {"count": n}, optionally step, span_step, copy_loads, tolerance
polar_array_selection {"count": n}, optionally axis, angle, origin, rotate_copies, full_circle, copy_loads, tolerance
plane_array_selection {"count1": n1, "count2": n2}, optionally plane, step1, step2, span_step, copy_loads, tolerance

Counts include the original, and steps are per copy. See Using ObjectiveFrame for what the options mean, and ChaiScript scripting for the same commands from a script.

Queries

Endpoint Body Returns
node_count {"value": n}
beam_count {"value": n}
node_pos_at {"index": i} {"pos": [x, y, z]}
beam_at {"index": i} {"i0": i0, "i1": i1}
find_node_near {"pos": [x, y, z], "tolerance": t} {"value": i}, or -1
is_node_fixed_at {"index": i} {"value": bool}
is_node_pos_bc_at {"index": i} {"value": bool}
is_node_selected_at {"index": i} {"value": bool}
has_node_load_at {"index": i} {"value": bool}
has_beam_load_at {"index": i} {"value": bool}
node_load_count {"value": n}
beam_load_count {"value": n}
material_count {"value": n}
model_bounds {"min": [...], "max": [...]}

The Python client

python/rest_client/ofapi.py wraps these endpoints in an ObjectiveFrame class, and can also start the application if it is not already running:

from ofapi import ObjectiveFrame

of = ObjectiveFrame()
of.start()

of.new_model()
of.add_nodes([[0, 0, 0], [4, 0, 0], [2, 3, 0]])
of.add_beams([[0, 1], [0, 2], [1, 2]])
of.assign_node_fixed_bc_ground()
of.add_node_load_at(2, 0.0, -1000.0, 0.0)

print(of.node_count(), "nodes")
print(of.model_bounds())

The client wraps the model, selection, boundary condition, load, array and query commands. The self-weight endpoints are currently only reachable by posting to them directly.