Ablative config
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useEngineDesign } from '../hooks/useEngineDesign.js'
|
||||
import { PropellantModal } from '../components/PropellantModal.jsx'
|
||||
import {
|
||||
exportEngineJSON,
|
||||
exportEngineOdt,
|
||||
@@ -13,6 +14,7 @@ import ErrorBoundary from '../components/ErrorBoundary.jsx'
|
||||
import { formatValue } from '../engine/format.js'
|
||||
import { getUnitsForFamily } from '../engine/units.js'
|
||||
import { ENGINE_FIELD_INFO } from '../engine/engineFieldInfo.js'
|
||||
import { ABLATIVE_MATERIALS } from '../engine/knowledgebaseData.js'
|
||||
|
||||
/* ── Info popup ───────────────────────────────────────────────────── */
|
||||
|
||||
@@ -231,6 +233,7 @@ function ResultSection({ title, children }) {
|
||||
|
||||
export default function EnginePage() {
|
||||
const importRef = useRef(null)
|
||||
const [showPropellants, setShowPropellants] = useState(false)
|
||||
const {
|
||||
thermoInputs, setThermoInput,
|
||||
chamber, setChamber,
|
||||
@@ -246,6 +249,7 @@ export default function EnginePage() {
|
||||
coolingResults: cr,
|
||||
feedResults: fr,
|
||||
loadDesign,
|
||||
applyPropellant,
|
||||
} = useEngineDesign()
|
||||
|
||||
function handleExportJSON() {
|
||||
@@ -320,7 +324,17 @@ export default function EnginePage() {
|
||||
{/* ── Left: Inputs ── */}
|
||||
<div className="w-[420px] shrink-0 overflow-y-auto p-4 border-r border-slate-700">
|
||||
|
||||
<DesignSection title="Thermodynamic Inputs">
|
||||
<DesignSection
|
||||
title="Thermodynamic Inputs"
|
||||
headerAction={
|
||||
<button
|
||||
onClick={() => setShowPropellants(true)}
|
||||
className="px-2 py-1 text-xs bg-blue-700 hover:bg-blue-600 text-white rounded transition-colors whitespace-nowrap"
|
||||
>
|
||||
⛽ Load Propellant
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<NumInput
|
||||
label="Chamber Pressure (p₀)"
|
||||
value={thermoInputs.p0}
|
||||
@@ -577,6 +591,24 @@ export default function EnginePage() {
|
||||
step="0.01"
|
||||
/>
|
||||
)}
|
||||
{cooling.method === 'ablative' && (
|
||||
<>
|
||||
<SelectInput
|
||||
label="Liner Material"
|
||||
value={cooling.ablativeMaterial}
|
||||
onChange={v => setCooling(c => ({ ...c, ablativeMaterial: v }))}
|
||||
options={ABLATIVE_MATERIALS.map(m => ({ value: m.id, label: m.name }))}
|
||||
/>
|
||||
<NumInput
|
||||
label="Initial Thickness"
|
||||
value={cooling.ablativeThickness}
|
||||
onChange={v => setCooling(c => ({ ...c, ablativeThickness: v }))}
|
||||
units="mm"
|
||||
step="1"
|
||||
placeholder="10"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DesignSection>
|
||||
|
||||
<DesignSection title="Feed System">
|
||||
@@ -694,6 +726,52 @@ export default function EnginePage() {
|
||||
<ResultRow label="Est. Isp Penalty" value={cr.ispPenalty} unit="%" infoKey="ispPenalty_result" />
|
||||
</>
|
||||
)}
|
||||
{cr.method === 'ablative' && cr.material && (
|
||||
<>
|
||||
<div className="text-sm mb-2">
|
||||
<span className="text-slate-400">Material: </span>
|
||||
<span className="text-slate-100 font-semibold">{cr.material.name}</span>
|
||||
</div>
|
||||
{cr.pressureFactor && Math.abs(cr.pressureFactor - 1.0) > 0.01 && (
|
||||
<ResultRow label="Pressure Correction" value={cr.pressureFactor} unit="×" />
|
||||
)}
|
||||
<ResultRow label="Erosion Rate" value={cr.effectiveRate} unit="mm/s" />
|
||||
<ResultRow label="Initial Thickness" value={cr.ablativeThickness} unit="mm" />
|
||||
<div className="flex items-center gap-2 text-sm py-1.5">
|
||||
<span className="text-slate-400 w-48 shrink-0">Eroded</span>
|
||||
<span className="font-mono text-green-400">
|
||||
{formatValue(cr.erosionMm)} mm
|
||||
</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
({formatValue(cr.erosionMmMin)}–{formatValue(cr.erosionMmMax)} mm)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm py-1.5">
|
||||
<span className="text-slate-400 w-48 shrink-0">Remaining Thickness</span>
|
||||
<span className={`font-mono font-semibold ${
|
||||
cr.status === 'critical' ? 'text-red-400' :
|
||||
cr.status === 'warning' ? 'text-amber-400' :
|
||||
'text-green-400'
|
||||
}`}>
|
||||
{formatValue(cr.remainingMm)} mm
|
||||
</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
(worst: {formatValue(cr.remainingMmWorst)} mm)
|
||||
</span>
|
||||
</div>
|
||||
{(cr.status === 'warning' || cr.status === 'critical') && (
|
||||
<div className={`text-xs px-3 py-2 rounded mt-2 ${
|
||||
cr.status === 'critical'
|
||||
? 'bg-red-900/30 border border-red-700 text-red-200'
|
||||
: 'bg-amber-900/30 border border-amber-700 text-amber-200'
|
||||
}`}>
|
||||
{cr.status === 'critical'
|
||||
? '🚨 CRITICAL: Liner thickness below safe minimum!'
|
||||
: '⚠️ WARNING: Liner thickness approaching minimum!'}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{cr.note && (
|
||||
<p className="text-xs text-slate-400 italic mt-1">{cr.note}</p>
|
||||
)}
|
||||
@@ -720,6 +798,14 @@ export default function EnginePage() {
|
||||
</ResultSection>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showPropellants && (
|
||||
<PropellantModal
|
||||
onClose={() => setShowPropellants(false)}
|
||||
onApply={applyPropellant}
|
||||
description="Select a propellant to pre-fill γ, R, T₀, O/F, and densities."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user