feat: Add Studio UI, intake system, and extractor improvements

Dashboard:
- Add Studio page with drag-drop model upload and Claude chat
- Add intake system for study creation workflow
- Improve session manager and context builder
- Add intake API routes and frontend components

Optimization Engine:
- Add CLI module for command-line operations
- Add intake module for study preprocessing
- Add validation module with gate checks
- Improve Zernike extractor documentation
- Update spec models with better validation
- Enhance solve_simulation robustness

Documentation:
- Add ATOMIZER_STUDIO.md planning doc
- Add ATOMIZER_UX_SYSTEM.md for UX patterns
- Update extractor library docs
- Add study-readme-generator skill

Tools:
- Add test scripts for extraction validation
- Add Zernike recentering test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 12:02:30 -05:00
parent 3193831340
commit a26914bbe8
56 changed files with 14173 additions and 646 deletions

View File

@@ -473,23 +473,33 @@ def extract_displacements_by_subcase(
ngt = darr.node_gridtype.astype(int)
node_ids = ngt if ngt.ndim == 1 else ngt[:, 0]
# Try to identify subcase from subtitle or isubcase
# Try to identify subcase from subtitle, label, or isubcase
subtitle = getattr(darr, 'subtitle', None)
op2_label = getattr(darr, 'label', None)
isubcase = getattr(darr, 'isubcase', None)
# Extract numeric from subtitle
label = None
if isinstance(subtitle, str):
import re
# Extract numeric from subtitle first, then label, then isubcase
import re
subcase_id = None
# Priority 1: subtitle (e.g., "GRAVITY 20 DEG")
if isinstance(subtitle, str) and subtitle.strip():
m = re.search(r'-?\d+', subtitle)
if m:
label = m.group(0)
subcase_id = m.group(0)
if label is None and isinstance(isubcase, int):
label = str(isubcase)
# Priority 2: label field (e.g., "90 SUBCASE 1")
if subcase_id is None and isinstance(op2_label, str) and op2_label.strip():
m = re.search(r'-?\d+', op2_label)
if m:
subcase_id = m.group(0)
if label:
result[label] = {
# Priority 3: isubcase number
if subcase_id is None and isinstance(isubcase, int):
subcase_id = str(isubcase)
if subcase_id:
result[subcase_id] = {
'node_ids': node_ids.astype(int),
'disp': dmat.copy()
}