Skip to main content

Python client for the Takeoff hydrofoil design platform

Project description

pytakeoff

Python client for the Takeoff hydrofoil design platform.

Everything the web app can do — projects, foil sections, analysis, optimization — scripted from Python. pytakeoff speaks the same WebSocket command protocol as the frontend, so every server command is available, and all permissions, credits, and rate limits are enforced server-side exactly as in the browser.

Install

pip install pytakeoff

Authentication — API keys, never passwords

Scripts authenticate with an API key — a token shaped like tk_<id>_<secret>, never your password. Get one either way:

  • In the GUI: Account → API Keys → Generate API key (shown once — copy it).

  • From the terminal (one-time interactive setup):

    python -m pytakeoff             # create a NEW key (asks for your password) and save it
    python -m pytakeoff configure   # save a key you ALREADY made in the GUI (paste it)
    

    The first form asks for your username/password once, creates a key over a single request, and saves it to ~/.takeoff/credentials. The password is never stored.

Once saved, no key in your code — TakeoffClient() picks it up:

from pytakeoff import TakeoffClient

with TakeoffClient() as client:          # key read from your saved credentials
    print(client.username)

TakeoffClient resolves the key, in order, from the api_key= argument, the TAKEOFF_API_KEY environment variable (recommended for CI), or your saved ~/.takeoff/credentials file. Never commit a key to a repository. Leaked a key? Revoke it in the GUI; your password and other keys are unaffected.

Quickstart

from pytakeoff import TakeoffClient

# Your API key (GUI → Account → API Keys). Or omit api_key= entirely and it is
# resolved from TAKEOFF_API_KEY / ~/.takeoff/credentials. Never commit a real key.
API_KEY = "tk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Connects to the official https://app.takeoff-technologies.com by default —
# no port needed. Self-hosted / dev: TakeoffClient("http://localhost", api_key=...)
# (plain-http URLs get port 8000 applied internally).
with TakeoffClient(api_key=API_KEY) as client:
    # Projects
    for p in client.projects.list():
        print(p["name"])

    project = client.projects.open("my_foil")

    # Entities (FoilSection, Wing, Bulb, Hull, Sail, ...)
    sections = project.foil_sections()
    project.update_entity("FoilSection", {"id": "<section-id>", "name": "renamed"})

    # 2D polar analysis (returns Plotly figure JSON among other data)
    polars = project.compute_polars(alpha_range=[-10, 10], reynolds_million=1.0)

    # Optimization with live progress
    result = project.optimize(
        "<optiaerofoil-entity-id>",
        on_progress=lambda pct, msg: print(f"{pct}% {msg or ''}"),
        timeout=None,  # wait as long as it takes
    )

    project.save()

Examples — simplest to more complex

Runnable scripts in examples/, numbered in learning order:

  1. 01_create_api_key.py — one-time setup: create an API key from the terminal (or use the GUI) and copy it into your scripts.
  2. 02_connect.py — connect and print who you are.
  3. 03_projects.py — list your projects and show the currently open one (client.projects.current()).
  4. 04_foil_section.py — the high-level FoilSection API: get/set control points, geometric parameters, structural parameters.
  5. 05_polar_analysis.py — 2D analysis: configure a sweep, run it, work with the raw numbers (incl. chordwise Cp).
  6. 06_optimization.py — 2D optimization: configure objectives/constraints, run with progress, inspect the result.

Prefer notebooks? The same three topics with plots: 07_foil_section.ipynb, 08_analysis_2d.ipynb, 09_optimization_2d.ipynb.

Foil sections

project.foil_sections() / project.foil_section(name) return high-level handles with three get/set pairs. Getters fetch live values; setters apply the change and return the achieved values (parametric setters refit the B-spline, so they can differ marginally from the request). Units match the GUI: tc, camber, te_thickness are percent of chord.

section = project.foil_section("main_foil")

cp = section.control_points()      # {"upper": [[x,y],...], "lower": [...], "degree", "n_coefs"}
section.set_control_points(upper=cp["upper"], lower=cp["lower"])

geo = section.geometry()           # {"tc", "camber", "le_radius", "te_thickness", "te_angle"}
section.set_geometry(tc=12.0, camber=2.5)

struct = section.structure()       # {"area", "Ixx", "SMx", "J", "centroid"(read-only)}
section.set_structure(area=struct["area"] * 1.1)

xy = section.points()              # the computed outline coordinates

2D analysis

project.analysis_2d(...) holds sweep parameters (alpha_range, reynolds_million, flap_range/flap_lock, ncrit, mach, solver, fluid, ...) and runs them over the visible foil sections:

analysis = project.analysis_2d(alpha_range=[-10, 10], reynolds_million=1.0, flap_lock=True)
result = analysis.run()       # raw arrays: alpha, Cl, Cd, Cm, Cl_Cd, ..., top_x/top_Cp (chordwise)
figures = analysis.figures()  # or the web app's ready-made Plotly figures

2D optimization

project.create_optimization_2d(...) / project.optimization_2d(name) return an Optimization2D handle on an OptiAeroFoil entity:

opt = project.create_optimization_2d("my_opt", initial_section="main_foil", solver="NN",
                                     optimizer_config={"maxiter": 100, "tol": 1e-3})
opt.set_objectives([{"func": "maxGlide", "Re": 1e6, "alpha": 4.0, "solve_for": "fixed"}])
opt.set_constraints(geo=[{"variable": "get_tc", "operator": ">", "value": 0.10}])

response = opt.run(on_progress=lambda pct, msg: print(pct, msg), timeout=None)

r = opt.result()              # latest run: opt_data (scores), opt_section (geometry)
opt.save_result()             # optimized section -> project as a FoilSection
opt.restore("1")              # reload a stored run; opt.runs(), opt.delete_runs([...])

Objective func is "minCd", "maxCl", "maxGlide", or any min<var>/max<var> over the polar variables; solve_for is "fixed", "alpha" (hit target_Cl), or "flap".

Every command, not just the facade

The facade (client.projects, Project) covers the common workflows. Everything else goes through the generic escape hatch — any of the server's registered commands can be called directly:

client.call("wing_set_span", wing={"id": wing_id}, span=1.25)
client.call("run_simulation", entity_id=sim_id, on_progress=print, timeout=None)

Discover what the server offers (fetched live, always in sync with the server version):

commands = client.commands()          # {name: {description, credit cost, ...}}
print(sorted(commands))

Server-initiated events

The server pushes broadcasts (saves by collaborators, property updates, job progress). Subscribe with:

client.on("project_saved", lambda msg: print("saved:", msg))
client.on("*", lambda msg: ...)  # everything

Handlers run while a call() is waiting for its response — keep them fast and don't issue new commands from inside one.

Error handling

import time
from pytakeoff import (
    CommandError, CommandTimeout, ConnectionClosed, GuiSessionActive, RateLimited,
)

try:
    client.call("run_optimization", entity_id=opt_id, timeout=3600)
except RateLimited as e:
    time.sleep(e.retry_after)                 # server throttled us; wait and retry
except GuiSessionActive:
    print("Close your browser session to switch projects from a script")
except CommandError as e:
    print("Server rejected:", e, e.payload)   # payload carries structured details
except CommandTimeout:
    print("Took too long")
except ConnectionClosed:
    client.reconnect()                        # re-exchanges the API key if needed

Good to know

  • Scripts are rate limited. API-key connections have per-account command budgets (a per-minute cap, plus a tighter hourly cap on heavy commands like run_simulation/run_optimization), one heavy command in flight at a time, and a small cap on concurrent connections. Limits scale with your plan. The browser GUI is not affected. Hitting a limit raises RateLimited with retry_after.
  • One session per user. The server keeps a single session per account, shared between your scripts and your browser. While your GUI is open, entity edits from a script are allowed (the GUI shows a "Script connected" chip and can refresh), but project switching/closing commands raise GuiSessionActive.
  • Credits & permissions apply exactly as in the web app; paid commands bill your account.
  • Binary mesh data: a few commands (get_entity_mesh, get_simulation_visualization, ...) answer with raw FlatBuffer bytes. pytakeoff returns them as a FlatBufferResult without decoding — most scripting workflows never need them.
  • Long sessions: session tokens expire after ~1 hour; if the connection drops, client.reconnect() re-exchanges your API key and reconnects.

Development

git clone https://github.com/takeoff-technologies/pytakeoff.git
cd pytakeoff
pip install -e ".[dev]"

License

Apache-2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytakeoff-0.1.1.tar.gz (238.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytakeoff-0.1.1-py3-none-any.whl (39.7 kB view details)

Uploaded Python 3

File details

Details for the file pytakeoff-0.1.1.tar.gz.

File metadata

  • Download URL: pytakeoff-0.1.1.tar.gz
  • Upload date:
  • Size: 238.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytakeoff-0.1.1.tar.gz
Algorithm Hash digest
SHA256 217c8fda573a21688af987cd71372886ef3a878aa133b34eec38ba83c41406dc
MD5 6c83f99c12b85bbf89ee64a8f4069c4f
BLAKE2b-256 3e27e87a3d5d744609c1c1e4fed0d47e7128ef2281f3c8e28e0f57f3349a6ebf

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytakeoff-0.1.1.tar.gz:

Publisher: publish.yml on takeoff-technologies/pytakeoff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pytakeoff-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pytakeoff-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 39.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytakeoff-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2b41f4ac3d6a10b6f293d7b565feab2e858c6c81be1af7c6b381d0c28831a4b7
MD5 96fb644f5ac167afd3cda573fccdc921
BLAKE2b-256 eff98f9314a4e57a79964f78bc052129c4cd4ce789903adde2add372a4467f9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytakeoff-0.1.1-py3-none-any.whl:

Publisher: publish.yml on takeoff-technologies/pytakeoff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page