feat: Add SIM file introspection journal and enhanced file-type specific UI
- Create introspect_sim.py NX journal to extract solutions, BCs from SIM files - Update introspect_sim_file() to optionally call NX journal for full introspection - Add FEM mesh section (nodes, elements, materials, properties) to IntrospectionPanel - Add SIM solutions and boundary conditions sections to IntrospectionPanel - Show introspection method and NX errors in panel
This commit is contained in:
@@ -23,6 +23,10 @@ import {
|
||||
File,
|
||||
Database,
|
||||
CheckCircle2,
|
||||
Layers,
|
||||
Grid3x3,
|
||||
Target,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { useCanvasStore } from '../../../hooks/useCanvasStore';
|
||||
|
||||
@@ -82,6 +86,45 @@ interface ExpressionsResult {
|
||||
user_count: number;
|
||||
}
|
||||
|
||||
// FEM file introspection result (from PyNastran)
|
||||
interface FemIntrospection {
|
||||
node_count?: number;
|
||||
element_count?: number;
|
||||
element_types?: Record<string, number>;
|
||||
materials?: Array<{ id: number; type: string; name?: string }>;
|
||||
properties?: Array<{ id: number; type: string; material_id?: number }>;
|
||||
coordinate_systems?: Array<{ id: number; type: string }>;
|
||||
load_sets?: number[];
|
||||
spc_sets?: number[];
|
||||
}
|
||||
|
||||
// SIM file introspection result (from NX journal)
|
||||
interface SimIntrospection {
|
||||
solutions?: Array<{
|
||||
name: string;
|
||||
type?: string;
|
||||
properties?: Record<string, unknown>;
|
||||
}>;
|
||||
boundary_conditions?: {
|
||||
constraints?: Array<{ name: string; type: string }>;
|
||||
loads?: Array<{ name: string; type: string }>;
|
||||
total_count?: number;
|
||||
};
|
||||
tree_structure?: {
|
||||
simulation_objects?: Array<{ pattern: string; type: string; found: boolean }>;
|
||||
found_types?: string[];
|
||||
};
|
||||
loaded_parts?: Array<{ name: string; type: string; leaf?: string }>;
|
||||
part_info?: {
|
||||
name?: string;
|
||||
is_assembly?: boolean;
|
||||
component_count?: number;
|
||||
components?: Array<{ name: string; type: string }>;
|
||||
};
|
||||
introspection_method?: string;
|
||||
nx_error?: string;
|
||||
}
|
||||
|
||||
interface IntrospectionResult {
|
||||
part_file?: string;
|
||||
part_path?: string;
|
||||
@@ -95,7 +138,7 @@ interface IntrospectionResult {
|
||||
dependent_files?: DependentFile[];
|
||||
extractors_available?: Extractor[];
|
||||
warnings?: string[];
|
||||
// Additional fields from NX introspection
|
||||
// Additional fields from NX introspection (PRT files)
|
||||
mass_properties?: Record<string, unknown>;
|
||||
materials?: Record<string, unknown>;
|
||||
bodies?: Record<string, unknown>;
|
||||
@@ -119,6 +162,18 @@ interface IntrospectionResult {
|
||||
op2: string[];
|
||||
};
|
||||
};
|
||||
// FEM file introspection (from PyNastran)
|
||||
fem?: FemIntrospection;
|
||||
// SIM file introspection (from NX journal)
|
||||
sim?: SimIntrospection;
|
||||
// Additional SIM fields that may be at top level
|
||||
solutions?: SimIntrospection['solutions'];
|
||||
boundary_conditions?: SimIntrospection['boundary_conditions'];
|
||||
tree_structure?: SimIntrospection['tree_structure'];
|
||||
loaded_parts?: SimIntrospection['loaded_parts'];
|
||||
part_info?: SimIntrospection['part_info'];
|
||||
introspection_method?: string;
|
||||
nx_error?: string;
|
||||
}
|
||||
|
||||
// Baseline run result interface
|
||||
@@ -165,7 +220,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(
|
||||
new Set(['expressions', 'extractors', 'file_deps', 'fea_results'])
|
||||
new Set(['expressions', 'extractors', 'file_deps', 'fea_results', 'fem_mesh', 'sim_solutions', 'sim_bcs'])
|
||||
);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
@@ -804,6 +859,219 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* FEM Mesh Info Section (for .fem/.afm files) */}
|
||||
{result.fem && (
|
||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleSection('fem_mesh')}
|
||||
className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Grid3x3 size={14} className="text-teal-400" />
|
||||
<span className="text-sm font-medium text-white">
|
||||
FEM Mesh ({result.fem.node_count?.toLocaleString() || 0} nodes)
|
||||
</span>
|
||||
</div>
|
||||
{expandedSections.has('fem_mesh') ? (
|
||||
<ChevronDown size={14} className="text-dark-400" />
|
||||
) : (
|
||||
<ChevronRight size={14} className="text-dark-400" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{expandedSections.has('fem_mesh') && (
|
||||
<div className="p-2 space-y-1">
|
||||
<div className="flex justify-between p-2 bg-dark-850 rounded text-xs">
|
||||
<span className="text-dark-400">Nodes</span>
|
||||
<span className="text-white font-mono">{result.fem.node_count?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between p-2 bg-dark-850 rounded text-xs">
|
||||
<span className="text-dark-400">Elements</span>
|
||||
<span className="text-white font-mono">{result.fem.element_count?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
|
||||
{/* Element types breakdown */}
|
||||
{result.fem.element_types && Object.keys(result.fem.element_types).length > 0 && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-dark-500 mb-1">Element Types:</p>
|
||||
{Object.entries(result.fem.element_types).map(([type, count]) => (
|
||||
<div key={type} className="flex justify-between p-1.5 bg-dark-800 rounded text-xs mb-0.5">
|
||||
<span className="text-teal-400 font-mono">{type}</span>
|
||||
<span className="text-white">{count}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Materials */}
|
||||
{result.fem.materials && result.fem.materials.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-dark-500 mb-1">Materials ({result.fem.materials.length}):</p>
|
||||
{result.fem.materials.slice(0, 5).map((mat) => (
|
||||
<div key={mat.id} className="flex justify-between p-1.5 bg-dark-800 rounded text-xs mb-0.5">
|
||||
<span className="text-blue-400">ID {mat.id}</span>
|
||||
<span className="text-white font-mono">{mat.type}</span>
|
||||
</div>
|
||||
))}
|
||||
{result.fem.materials.length > 5 && (
|
||||
<p className="text-xs text-dark-500 text-center">+{result.fem.materials.length - 5} more</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Properties */}
|
||||
{result.fem.properties && result.fem.properties.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-dark-500 mb-1">Properties ({result.fem.properties.length}):</p>
|
||||
{result.fem.properties.slice(0, 5).map((prop) => (
|
||||
<div key={prop.id} className="flex justify-between p-1.5 bg-dark-800 rounded text-xs mb-0.5">
|
||||
<span className="text-amber-400">ID {prop.id}</span>
|
||||
<span className="text-white font-mono">{prop.type}</span>
|
||||
</div>
|
||||
))}
|
||||
{result.fem.properties.length > 5 && (
|
||||
<p className="text-xs text-dark-500 text-center">+{result.fem.properties.length - 5} more</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Load/SPC sets */}
|
||||
{(result.fem.load_sets?.length || result.fem.spc_sets?.length) && (
|
||||
<div className="mt-2 flex gap-2">
|
||||
{result.fem.load_sets && result.fem.load_sets.length > 0 && (
|
||||
<div className="flex-1 p-1.5 bg-dark-800 rounded text-xs">
|
||||
<span className="text-dark-400">Load Sets: </span>
|
||||
<span className="text-green-400">{result.fem.load_sets.join(', ')}</span>
|
||||
</div>
|
||||
)}
|
||||
{result.fem.spc_sets && result.fem.spc_sets.length > 0 && (
|
||||
<div className="flex-1 p-1.5 bg-dark-800 rounded text-xs">
|
||||
<span className="text-dark-400">SPC Sets: </span>
|
||||
<span className="text-red-400">{result.fem.spc_sets.join(', ')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SIM Solutions Section (for .sim files) */}
|
||||
{(result.solutions || result.sim?.solutions) && (
|
||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleSection('sim_solutions')}
|
||||
className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Target size={14} className="text-violet-400" />
|
||||
<span className="text-sm font-medium text-white">
|
||||
Solutions ({(result.solutions || result.sim?.solutions)?.length || 0})
|
||||
</span>
|
||||
</div>
|
||||
{expandedSections.has('sim_solutions') ? (
|
||||
<ChevronDown size={14} className="text-dark-400" />
|
||||
) : (
|
||||
<ChevronRight size={14} className="text-dark-400" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{expandedSections.has('sim_solutions') && (
|
||||
<div className="p-2 space-y-1">
|
||||
{((result.solutions || result.sim?.solutions) || []).map((sol, idx) => (
|
||||
<div key={idx} className="p-2 bg-dark-850 rounded">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap size={12} className="text-violet-400" />
|
||||
<span className="text-sm text-white">{sol.name}</span>
|
||||
</div>
|
||||
{sol.type && (
|
||||
<p className="text-xs text-dark-400 mt-1">Type: {sol.type}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{(result.solutions || result.sim?.solutions)?.length === 0 && (
|
||||
<p className="text-xs text-dark-500 text-center py-2">No solutions found</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SIM Boundary Conditions Section */}
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions) && (
|
||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleSection('sim_bcs')}
|
||||
className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Layers size={14} className="text-rose-400" />
|
||||
<span className="text-sm font-medium text-white">
|
||||
Boundary Conditions ({(result.boundary_conditions || result.sim?.boundary_conditions)?.total_count || 0})
|
||||
</span>
|
||||
</div>
|
||||
{expandedSections.has('sim_bcs') ? (
|
||||
<ChevronDown size={14} className="text-dark-400" />
|
||||
) : (
|
||||
<ChevronRight size={14} className="text-dark-400" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{expandedSections.has('sim_bcs') && (
|
||||
<div className="p-2 space-y-2">
|
||||
{/* Constraints */}
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions)?.constraints &&
|
||||
(result.boundary_conditions || result.sim?.boundary_conditions)!.constraints!.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-dark-500 mb-1">Constraints:</p>
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions)!.constraints!.map((bc, idx) => (
|
||||
<div key={idx} className="flex justify-between p-1.5 bg-dark-800 rounded text-xs mb-0.5">
|
||||
<span className="text-rose-400">{bc.name}</span>
|
||||
<span className="text-dark-400">{bc.type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loads */}
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions)?.loads &&
|
||||
(result.boundary_conditions || result.sim?.boundary_conditions)!.loads!.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-dark-500 mb-1">Loads:</p>
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions)!.loads!.map((load, idx) => (
|
||||
<div key={idx} className="flex justify-between p-1.5 bg-dark-800 rounded text-xs mb-0.5">
|
||||
<span className="text-green-400">{load.name}</span>
|
||||
<span className="text-dark-400">{load.type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(result.boundary_conditions || result.sim?.boundary_conditions)?.total_count === 0 && (
|
||||
<p className="text-xs text-dark-500 text-center py-2">No boundary conditions found</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SIM Introspection Method Info */}
|
||||
{(result.introspection_method || result.nx_error) && (
|
||||
<div className="p-2 bg-dark-800 rounded-lg text-xs">
|
||||
{result.introspection_method && (
|
||||
<p className="text-dark-400">
|
||||
Method: <span className="text-white">{result.introspection_method}</span>
|
||||
</p>
|
||||
)}
|
||||
{result.nx_error && (
|
||||
<p className="text-amber-400 mt-1">
|
||||
NX Error: {result.nx_error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* File Dependencies Section (NX file chain) */}
|
||||
{result.file_dependencies && (
|
||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||
|
||||
Reference in New Issue
Block a user