feat(canvas): Add file browser, introspection, and improve node flow

Phase 1-7 of Canvas V4 Ralph Loop implementation:

Backend:
- Add /api/files routes for browsing model files
- Add /api/nx routes for NX model introspection
- Add NXIntrospector service to discover expressions and extractors
- Add health check with database status

Frontend:
- Add FileBrowser component for selecting .sim/.prt/.fem files
- Add IntrospectionPanel to discover expressions and extractors
- Update NodeConfigPanel with browse and introspect buttons
- Update schema with NODE_HANDLES for proper flow direction
- Update validation for correct DesignVar -> Model -> Solver flow
- Update useCanvasStore.addNode() to accept custom data

Flow correction: Design Variables now connect TO Model (as source),
not FROM Model. This matches the actual data flow in optimization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 14:47:10 -05:00
parent 62284a995e
commit 1c7c7aff05
13 changed files with 4401 additions and 25 deletions

View File

@@ -0,0 +1,90 @@
"""
NX API Routes
Provides NX model introspection capabilities for the Canvas Builder.
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
router = APIRouter()
class IntrospectRequest(BaseModel):
file_path: str
@router.post("/introspect")
async def introspect_model(request: IntrospectRequest):
"""
Introspect an NX model file to discover expressions, solver type, and dependencies.
Args:
file_path: Relative path from studies root (e.g., "M1_Mirror/study_v1/model.sim")
Returns:
Introspection result with expressions, solver_type, dependent_files, extractors
"""
try:
from api.services.nx_introspection import NXIntrospector
introspector = NXIntrospector(request.file_path)
result = introspector.introspect()
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/expressions")
async def get_expressions(file_path: str):
"""
Get expressions from an NX model.
Args:
file_path: Relative path from studies root
Returns:
List of expressions with names, values, units
"""
try:
from api.services.nx_introspection import NXIntrospector
introspector = NXIntrospector(file_path)
result = introspector.introspect()
return {
"expressions": result.get("expressions", []),
"file_path": file_path,
"source": "introspection",
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/extractors")
async def list_extractors(solver_type: Optional[str] = None):
"""
List available extractors, optionally filtered by solver type.
Args:
solver_type: Optional solver type (SOL101, SOL103, etc.)
Returns:
List of available extractors with their descriptions
"""
from api.services.nx_introspection import NXIntrospector
# Create a dummy introspector to get extractor suggestions
class DummyIntrospector:
def __init__(self):
self.parent_dir = ""
dummy = NXIntrospector.__new__(NXIntrospector)
dummy.parent_dir = ""
extractors = dummy._suggest_extractors(solver_type)
return {
"extractors": extractors,
"solver_type": solver_type,
}