diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/SomhairlesDream.Cli/Arguments.fs | 57 | ||||
| -rw-r--r-- | src/SomhairlesDream.Cli/Program.fs | 1 | ||||
| -rw-r--r-- | src/SomhairlesDream.Modeling/BlenderBridge.fs | 13 | ||||
| -rw-r--r-- | src/SomhairlesDream.Modeling/Definitions.fs | 65 | ||||
| -rw-r--r-- | src/SomhairlesDream.Modeling/Pipeline.fs | 13 | ||||
| -rw-r--r-- | src/SomhairlesDream.Server/ArtifactRunCoordinator.fs | 1 |
6 files changed, 128 insertions, 22 deletions
diff --git a/src/SomhairlesDream.Cli/Arguments.fs b/src/SomhairlesDream.Cli/Arguments.fs index 2de96be..fa9087b 100644 --- a/src/SomhairlesDream.Cli/Arguments.fs +++ b/src/SomhairlesDream.Cli/Arguments.fs @@ -9,7 +9,8 @@ type RunOptions = OutputRoot: string StageRoot: string Python: string - Render: bool } + Render: bool + Design: string } type Command = | Run of RunOptions @@ -19,22 +20,32 @@ type Command = module Arguments = let usage = "usage: dotnet run --project src/SomhairlesDream.Cli -- run [options]\n" - + " dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>" + + " dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>\n" + + "options: --project-id --run-id --base-id --target-id --output-root --stage-root --python --render --design heritage|probe" let private defaultPython () = let configured = Environment.GetEnvironmentVariable("SOMHAIRLES_BPYTHON") if String.IsNullOrWhiteSpace(configured) then "python3" else configured + let private heritageIds = + { ProjectId = "heritage-001" + RunId = "run-001" + BaseId = "base-000" + TargetId = "target-003" } + + let private probeIds = + { ProjectId = "probe-001" + RunId = "run-001" + BaseId = "probe-base-000" + TargetId = "probe-target-007" } + let private defaults () = - { Ids = - { ProjectId = "heritage-001" - RunId = "run-001" - BaseId = "base-000" - TargetId = "target-003" } + { Ids = heritageIds OutputRoot = ".artifacts" StageRoot = "stage" Python = defaultPython () - Render = false } + Render = false + Design = "heritage" } let private requiredValue (args: string array) (index: int) (optionName: string) = if index + 1 >= args.Length || String.IsNullOrWhiteSpace(args[index + 1]) then @@ -51,14 +62,34 @@ module Arguments = | "--output-root" -> Ok { options with OutputRoot = value } | "--stage-root" -> Ok { options with StageRoot = value } | "--python" -> Ok { options with Python = value } + | "--design" -> Ok { options with Design = value } | _ -> Error $"unknown option: {optionName}" + let private resolveIds (options: RunOptions) = + if options.Design = "probe" then + let swap heritage probe value = if value = heritage then probe else value + + let ids = + { ProjectId = swap heritageIds.ProjectId probeIds.ProjectId options.Ids.ProjectId + RunId = swap heritageIds.RunId probeIds.RunId options.Ids.RunId + BaseId = swap heritageIds.BaseId probeIds.BaseId options.Ids.BaseId + TargetId = swap heritageIds.TargetId probeIds.TargetId options.Ids.TargetId } + + { options with Ids = ids } + else + options + let private validateRunOptions (options: RunOptions) = - [| options.Ids.ProjectId; options.Ids.RunId; options.Ids.BaseId; options.Ids.TargetId |] - |> Array.tryFind (Ids.validateResourceId >> Result.isError) - |> function - | Some value -> Error $"invalid resource id: {value}" - | None -> Ok(Run options) + if options.Design <> "heritage" && options.Design <> "probe" then + Error $"invalid design: {options.Design}" + else + let resolved = resolveIds options + + [| resolved.Ids.ProjectId; resolved.Ids.RunId; resolved.Ids.BaseId; resolved.Ids.TargetId |] + |> Array.tryFind (Ids.validateResourceId >> Result.isError) + |> function + | Some value -> Error $"invalid resource id: {value}" + | None -> Ok(Run resolved) let private parseRun (args: string array) = let rec loop (index: int) (options: RunOptions) = diff --git a/src/SomhairlesDream.Cli/Program.fs b/src/SomhairlesDream.Cli/Program.fs index 0d9ed1c..dd58ff0 100644 --- a/src/SomhairlesDream.Cli/Program.fs +++ b/src/SomhairlesDream.Cli/Program.fs @@ -14,6 +14,7 @@ module Program = let pipelineOptions : PipelineOptions = { ArtifactRoot = options.OutputRoot Ids = options.Ids + Steps = if options.Design = "probe" then Design.probeSteps else Design.steps Render = options.Render Bridge = bridge Clock = fun () -> DateTimeOffset.UtcNow diff --git a/src/SomhairlesDream.Modeling/BlenderBridge.fs b/src/SomhairlesDream.Modeling/BlenderBridge.fs index c0795e9..c4215ae 100644 --- a/src/SomhairlesDream.Modeling/BlenderBridge.fs +++ b/src/SomhairlesDream.Modeling/BlenderBridge.fs @@ -101,8 +101,13 @@ type BlenderProcessBridge(python: string, stageRoot: string, ?timeout: TimeSpan) File.WriteAllText(path, content) - member private _.StartInfo(request: BridgeRequest) = - let scriptPath = Path.GetFullPath(Path.Combine(stageRoot, "blender", "fixture.py")) + member private _.ScriptPath(stepId: string) = + let scriptName = if stepId.StartsWith("probe-") then "probe.py" else "fixture.py" + + Path.GetFullPath(Path.Combine(stageRoot, "blender", scriptName)) + + member private this.StartInfo(request: BridgeRequest) = + let scriptPath = this.ScriptPath(request.Step.StepId) let info = ProcessStartInfo() info.FileName <- python info.UseShellExecute <- false @@ -128,10 +133,10 @@ type BlenderProcessBridge(python: string, stageRoot: string, ?timeout: TimeSpan) interface IArtifactBridge with member this.Export(request, heartbeat) = try - let scriptPath = Path.GetFullPath(Path.Combine(stageRoot, "blender", "fixture.py")) + let scriptPath = this.ScriptPath(request.Step.StepId) if not (File.Exists(scriptPath)) then - Error(bridgeError $"fixture is missing: {scriptPath}") + Error(bridgeError $"blender script is missing: {scriptPath}") else match Path.GetDirectoryName(request.GlbPath) with | null -> () diff --git a/src/SomhairlesDream.Modeling/Definitions.fs b/src/SomhairlesDream.Modeling/Definitions.fs index 64e6bc6..9a9256e 100644 --- a/src/SomhairlesDream.Modeling/Definitions.fs +++ b/src/SomhairlesDream.Modeling/Definitions.fs @@ -29,3 +29,68 @@ module Design = Changes = [| { ObjectId = "heritage.cabin"; Kind = "add"; Summary = "Add cabin volume" } { ObjectId = "heritage.crossbeam"; Kind = "add"; Summary = "Add cabin crossbeam" } |] } |] + + // YJ-01 Voyager-class current-tech probe (source: interstellar-dual-track/current-tech-probe + // scripts/build_probe.py, line ranges cited per step). Object ids map the legacy + // UPPER_SNAKE names onto lowercase dotted ids via documented rule + // (probe.bus.bay.panel.00 <- BUS_BAY_PANEL_00, probe.rtg.01.fin.00 <- RTG_01_FIN_0). + // Display plinth, information card and MODEL_LABEL are intentionally excluded. + let probeSteps : DesignStep array = + let bus = + [| "probe.bus.electronics.decagon"; "probe.bus.ring.front"; "probe.bus.ring.rear" + "probe.bus.bay.panel.00"; "probe.bus.bay.panel.01"; "probe.bus.bay.panel.02" + "probe.bus.bay.panel.03"; "probe.bus.bay.panel.04"; "probe.bus.bay.panel.05" + "probe.bus.bay.panel.06"; "probe.bus.bay.panel.07"; "probe.bus.bay.panel.08" + "probe.bus.bay.panel.09" + "probe.bus.mli.top"; "probe.bus.mli.bottom" + "probe.bus.radiator.port"; "probe.bus.radiator.starboard" + "probe.aacs" + "probe.thruster.01"; "probe.thruster.02"; "probe.thruster.03"; "probe.thruster.04" |] + let booms = + [| "probe.rtg.boom" + "probe.mag.boom"; "probe.mag.head"; "probe.mag.sensor.a"; "probe.mag.sensor.b" + "probe.pws.antenna.left"; "probe.pws.antenna.right" + "probe.pws.tip.left"; "probe.pws.tip.right" |] + let rtgs = + [| "probe.rtg.01"; "probe.rtg.01.fin.00"; "probe.rtg.01.fin.01"; "probe.rtg.01.fin.02" + "probe.rtg.02"; "probe.rtg.02.fin.00"; "probe.rtg.02.fin.01"; "probe.rtg.02.fin.02" + "probe.rtg.03"; "probe.rtg.03.fin.00"; "probe.rtg.03.fin.01"; "probe.rtg.03.fin.02" |] + let hga = + [| "probe.hga.reflector"; "probe.hga.feed" + "probe.hga.strut.00"; "probe.hga.strut.01"; "probe.hga.strut.02"; "probe.hga.strut.03" |] + let instruments = + [| "probe.scan.pivot"; "probe.scan.platform" + "probe.crs"; "probe.plasma"; "probe.optical"; "probe.optical.aperture" |] + let mast = [| "probe.science.boom" |] + let prior (prefix: string array) (current: string array) = + Array.append prefix current + + let busChanges = bus |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add ten-sided bus with bay panels, MLI and radiators" }) + let boomChanges = booms |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add fields-and-particles and RTG support booms" }) + let rtgChanges = rtgs |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add three RTGs with heat-rejection fins" }) + let hgaChanges = hga |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add high-gain antenna, feed and struts" }) + let instrumentChanges = instruments |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add articulated scan platform with science instruments" }) + let mastChanges = mast |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add deployable science mast" }) + let liveryChanges = + [| { ObjectId = "probe.hga.reflector"; Kind = "update"; Summary = "Livery pass: reflector white finish" } + { ObjectId = "probe.bus.mli.top"; Kind = "update"; Summary = "Livery pass: MLI gold foil finish" } + { ObjectId = "probe.bus.bay.panel.01"; Kind = "update"; Summary = "Livery pass: restrained red accents on bay panels" } + { ObjectId = "probe.pws.tip.left"; Kind = "update"; Summary = "Livery pass: red antenna tip markers" } + { ObjectId = "probe.bus.electronics.decagon"; Kind = "update"; Summary = "Livery pass: body blue basecoat" } + { ObjectId = "probe.rtg.01"; Kind = "update"; Summary = "Livery pass: graphite RTG housings" } |] + + [| { StepId = "probe-01-bus"; Index = 0; ObjectIds = bus; Changes = busChanges } + { StepId = "probe-02-boom"; Index = 1; ObjectIds = prior bus booms; Changes = boomChanges } + { StepId = "probe-03-rtg"; Index = 2; ObjectIds = prior (prior bus booms) rtgs; Changes = rtgChanges } + { StepId = "probe-04-hga"; Index = 3; ObjectIds = prior (prior (prior bus booms) rtgs) hga; Changes = hgaChanges } + { StepId = "probe-05-instruments"; Index = 4 + ObjectIds = prior (prior (prior (prior bus booms) rtgs) hga) instruments; Changes = instrumentChanges } + { StepId = "probe-06-mast"; Index = 5 + ObjectIds = prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast + Changes = mastChanges } + { StepId = "probe-07-livery"; Index = 6 + ObjectIds = prior (prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast) [||] + Changes = liveryChanges } |] + + let forProject (projectId: string) : DesignStep array = + if projectId.StartsWith("probe-") then probeSteps else steps diff --git a/src/SomhairlesDream.Modeling/Pipeline.fs b/src/SomhairlesDream.Modeling/Pipeline.fs index 3f1f105..d9db5f6 100644 --- a/src/SomhairlesDream.Modeling/Pipeline.fs +++ b/src/SomhairlesDream.Modeling/Pipeline.fs @@ -33,6 +33,7 @@ type IArtifactBridge = type PipelineOptions = { ArtifactRoot: string Ids: RunIds + Steps: DesignStep array Render: bool Bridge: IArtifactBridge Clock: unit -> DateTimeOffset @@ -214,10 +215,12 @@ module Pipeline = let emit event = appendEvent log options.OnEvent event let ids = options.Ids - emit (RunEvent.Start { Ids = ids; At = startedAt; TotalSteps = Design.steps.Length }) + let steps = options.Steps + + emit (RunEvent.Start { Ids = ids; At = startedAt; TotalSteps = steps.Length }) let artifacts = - [| for step in Design.steps do + [| for step in steps do currentStep <- Some step.StepId emit (RunEvent.Heartbeat { Ids = ids; At = options.Clock(); Message = $"starting {step.StepId}" }) @@ -275,7 +278,7 @@ module Pipeline = At = options.Clock() StepId = step.StepId StepIndex = step.Index - TotalSteps = Design.steps.Length + TotalSteps = steps.Length Message = "artifact verified" ArtifactPath = relativePath } @@ -424,12 +427,12 @@ module ArtifactVerifier = || Ids.validateResourceId manifest.BaseId |> Result.isError || Ids.validateResourceId manifest.TargetId |> Result.isError then Error "manifest contains an invalid resource id" - elif manifest.Steps.Length <> Design.steps.Length then + elif manifest.Steps.Length <> (Design.forProject manifest.ProjectId).Length then Error "manifest step count does not match design" else let mutable failure: string option = None - for expected, actual in Array.zip Design.steps manifest.Steps do + for expected, actual in Array.zip (Design.forProject manifest.ProjectId) manifest.Steps do if failure.IsNone && actual.StepId <> expected.StepId then failure <- Some $"manifest step id mismatch: {expected.StepId}" elif failure.IsNone && actual.StepIndex <> expected.Index then diff --git a/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs b/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs index 022ac7d..07ac984 100644 --- a/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs +++ b/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs @@ -217,6 +217,7 @@ type ArtifactRunCoordinator( let options : PipelineOptions = { ArtifactRoot = root Ids = ids + Steps = Design.forProject ids.ProjectId Render = render Bridge = bridgeFactory () Clock = clock |
