added preset saving and heat transfer
This commit is contained in:
130
app.py
130
app.py
@@ -9,13 +9,14 @@ POST /api/sweep → run sweep_operating_points(), return JSON dataset
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
|
||||
from designer import ToroidCore, WindingSpec, design_transformer
|
||||
from designer import ToroidCore, WindingSpec, WireSpec, design_transformer
|
||||
from sim_toroid import (
|
||||
ToroidSimulator, SimConstraints,
|
||||
sweep_operating_points, SweepEntry,
|
||||
@@ -61,12 +62,30 @@ def _parse_windings(data: dict) -> list[WindingSpec]:
|
||||
return specs
|
||||
|
||||
|
||||
def _drawing_b64(core: ToroidCore, results) -> str:
|
||||
def _actual_fill_factor(core: ToroidCore, results) -> float:
|
||||
"""
|
||||
Compute actual fill factor = total wire cross-section area placed / window area.
|
||||
Sums wire area for every turn placed across all windings.
|
||||
"""
|
||||
window_area = core.window_area_mm2
|
||||
if window_area <= 0:
|
||||
return 0.0
|
||||
total_wire_area = 0.0
|
||||
for wr in results:
|
||||
for seg in wr.segments:
|
||||
wire = WireSpec.from_awg(seg.awg)
|
||||
r = wire.diameter_mm / 2.0
|
||||
total_wire_area += math.pi * r * r * seg.turns
|
||||
return total_wire_area / window_area
|
||||
|
||||
|
||||
def _drawing_b64(core: ToroidCore, results, actual_fill: float) -> str:
|
||||
"""Render the toroid PNG and return it as a base64 data-URI string."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
|
||||
tmp_path = f.name
|
||||
try:
|
||||
draw_toroid(core, results, output_path=tmp_path, dpi=150, fig_size_mm=160)
|
||||
draw_toroid(core, results, output_path=tmp_path, dpi=150, fig_size_mm=160,
|
||||
actual_fill=actual_fill)
|
||||
with open(tmp_path, "rb") as f:
|
||||
png_bytes = f.read()
|
||||
finally:
|
||||
@@ -156,13 +175,28 @@ def api_design():
|
||||
|
||||
results = design_transformer(core, specs, fill_factor=fill_factor)
|
||||
|
||||
drawing_b64 = _drawing_b64(core, results)
|
||||
actual_fill = _actual_fill_factor(core, results)
|
||||
drawing_b64 = _drawing_b64(core, results, actual_fill)
|
||||
windings_info = [_winding_result_to_dict(wr) for wr in results]
|
||||
|
||||
# Thermal surface area of wound toroid (m²):
|
||||
# outer cylinder + top/bottom annular faces + inner bore cylinder
|
||||
OD_m = core.OD_mm * 1e-3
|
||||
ID_m = core.ID_mm * 1e-3
|
||||
H_m = core.height_mm * 1e-3
|
||||
A_outer = math.pi * OD_m * H_m
|
||||
A_faces = 2 * math.pi / 4 * (OD_m**2 - ID_m**2)
|
||||
A_bore = math.pi * ID_m * H_m
|
||||
A_surface_m2 = A_outer + A_faces + A_bore
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"windings": windings_info,
|
||||
"drawing": drawing_b64,
|
||||
"fill_factor_target": fill_factor,
|
||||
"fill_factor_actual": round(actual_fill, 4),
|
||||
"window_area_mm2": round(core.window_area_mm2, 2),
|
||||
"A_surface_m2": round(A_surface_m2, 6),
|
||||
})
|
||||
|
||||
except Exception as exc:
|
||||
@@ -259,5 +293,93 @@ def api_sweep():
|
||||
"traceback": traceback.format_exc()}), 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Preset persistence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PRESETS_DIR = os.path.join(os.path.dirname(__file__), "presets")
|
||||
os.makedirs(PRESETS_DIR, exist_ok=True)
|
||||
|
||||
# Valid preset categories
|
||||
_PRESET_TYPES = {"core", "windings", "constraints", "sim"}
|
||||
|
||||
|
||||
def _preset_path(ptype: str) -> str:
|
||||
return os.path.join(PRESETS_DIR, f"{ptype}.json")
|
||||
|
||||
|
||||
def _load_store(ptype: str) -> dict:
|
||||
"""Load the JSON store for a preset type; return {} on missing/corrupt."""
|
||||
path = _preset_path(ptype)
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _save_store(ptype: str, store: dict) -> None:
|
||||
path = _preset_path(ptype)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(store, f, indent=2)
|
||||
|
||||
|
||||
@app.route("/api/presets/<ptype>", methods=["GET"])
|
||||
def presets_list(ptype: str):
|
||||
"""Return list of saved preset names and the last-used name."""
|
||||
if ptype not in _PRESET_TYPES:
|
||||
return jsonify({"success": False, "error": f"Unknown type '{ptype}'"}), 400
|
||||
store = _load_store(ptype)
|
||||
names = [k for k in store if not k.startswith("_")]
|
||||
last = store.get("_last", None)
|
||||
return jsonify({"success": True, "names": sorted(names), "last": last})
|
||||
|
||||
|
||||
@app.route("/api/presets/<ptype>/<name>", methods=["GET"])
|
||||
def presets_load(ptype: str, name: str):
|
||||
"""Load a named preset and mark it as last-used."""
|
||||
if ptype not in _PRESET_TYPES:
|
||||
return jsonify({"success": False, "error": f"Unknown type '{ptype}'"}), 400
|
||||
store = _load_store(ptype)
|
||||
if name not in store:
|
||||
return jsonify({"success": False, "error": f"Preset '{name}' not found"}), 404
|
||||
store["_last"] = name
|
||||
_save_store(ptype, store)
|
||||
return jsonify({"success": True, "name": name, "data": store[name]})
|
||||
|
||||
|
||||
@app.route("/api/presets/<ptype>/<name>", methods=["POST"])
|
||||
def presets_save(ptype: str, name: str):
|
||||
"""Save (create or overwrite) a named preset and mark it as last-used."""
|
||||
if ptype not in _PRESET_TYPES:
|
||||
return jsonify({"success": False, "error": f"Unknown type '{ptype}'"}), 400
|
||||
if not name or name.startswith("_"):
|
||||
return jsonify({"success": False, "error": "Invalid preset name"}), 400
|
||||
data = request.get_json(force=True)
|
||||
store = _load_store(ptype)
|
||||
store[name] = data
|
||||
store["_last"] = name
|
||||
_save_store(ptype, store)
|
||||
return jsonify({"success": True, "name": name})
|
||||
|
||||
|
||||
@app.route("/api/presets/<ptype>/<name>", methods=["DELETE"])
|
||||
def presets_delete(ptype: str, name: str):
|
||||
"""Delete a named preset."""
|
||||
if ptype not in _PRESET_TYPES:
|
||||
return jsonify({"success": False, "error": f"Unknown type '{ptype}'"}), 400
|
||||
store = _load_store(ptype)
|
||||
if name not in store:
|
||||
return jsonify({"success": False, "error": f"Preset '{name}' not found"}), 404
|
||||
del store[name]
|
||||
if store.get("_last") == name:
|
||||
remaining = [k for k in store if not k.startswith("_")]
|
||||
store["_last"] = remaining[-1] if remaining else None
|
||||
_save_store(ptype, store)
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, host="0.0.0.0", port=5000)
|
||||
|
||||
Reference in New Issue
Block a user