fix(canvas): Multiple fixes for drag-drop, undo/redo, and code generation

Drag-drop fixes:
- Fix Objective default data: use nested 'source' object with extractor_id/output_name
- Fix Constraint default data: use 'type' field (not constraint_type), 'threshold' (not limit)

Undo/Redo fixes:
- Remove dependency on isDirty flag (which is always false due to auto-save)
- Record snapshots based on actual spec changes via deep comparison

Code generation improvements:
- Update system prompt to support multiple extractor types:
  * OP2-based extractors for FEA results (stress, displacement, frequency)
  * Expression-based extractors for NX model values (dimensions, volumes)
  * Computed extractors for derived values (no FEA needed)
- Claude will now choose appropriate signature based on user's description
This commit is contained in:
2026-01-20 15:08:49 -05:00
parent 89694088a2
commit 5c419e2358
30 changed files with 1781 additions and 85 deletions

View File

@@ -83,23 +83,49 @@ async def generate_extractor_code(request: ExtractorGenerationRequest):
# Build focused system prompt for extractor generation
system_prompt = """You are generating a Python custom extractor function for Atomizer FEA optimization.
The function MUST:
1. Have signature: def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict
2. Return a dict with extracted values (e.g., {"max_stress": 150.5, "mass": 2.3})
3. Use pyNastran.op2.op2.OP2 for reading OP2 results
4. Handle missing data gracefully with try/except blocks
IMPORTANT: Choose the appropriate function signature based on what data is needed:
Available imports (already available, just use them):
- from pyNastran.op2.op2 import OP2
- import numpy as np
- from pathlib import Path
## Option 1: FEA Results (OP2) - Use for stresses, displacements, frequencies, forces
```python
def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict:
from pyNastran.op2.op2 import OP2
op2 = OP2()
op2.read_op2(op2_path)
# Access: op2.displacements[subcase_id], op2.cquad4_stress[subcase_id], etc.
return {"max_stress": value}
```
Common patterns:
- Displacement: op2.displacements[subcase_id].data[0, :, 1:4] (x,y,z components)
## Option 2: Expression/Computed Values (no FEA needed) - Use for dimensions, volumes, derived values
```python
def extract(trial_dir: str, config: dict, context: dict) -> dict:
import json
from pathlib import Path
# Read mass properties (if available from model introspection)
mass_file = Path(trial_dir) / "mass_properties.json"
if mass_file.exists():
with open(mass_file) as f:
props = json.load(f)
mass = props.get("mass_kg", 0)
# Or use config values directly (e.g., expression values)
length_mm = config.get("length_expression", 100)
# context has results from other extractors
other_value = context.get("other_extractor_output", 0)
return {"computed_value": length_mm * 2}
```
Available imports: pyNastran.op2.op2.OP2, numpy, pathlib.Path, json
Common OP2 patterns:
- Displacement: op2.displacements[subcase_id].data[0, :, 1:4] (x,y,z)
- Stress: op2.cquad4_stress[subcase_id] or op2.ctria3_stress[subcase_id]
- Eigenvalues: op2.eigenvalues[subcase_id]
- Mass: op2.grid_point_weight (if available)
Return ONLY the complete Python code wrapped in ```python ... ```. No explanations outside the code block."""
Return ONLY the complete Python code wrapped in ```python ... ```. No explanations."""
# Build user prompt with context
user_prompt = f"Generate a custom extractor that: {request.prompt}"