Skip to content

Commit 8bf83b8

Browse files
committed
feat(core): full fmu 2.0 archive export with model exchange, co-simulation c codegen, and zip packaging
1 parent 565d3bb commit 8bf83b8

5 files changed

Lines changed: 1221 additions & 40 deletions

File tree

packages/cli/src/commands/export-fmu.ts

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// SPDX-License-Identifier: AGPL-3.0-or-later
22

3-
import type { FmuOptions } from "@modelscript/core";
3+
import type { FmuArchiveOptions } from "@modelscript/core";
44
import {
55
Context,
66
ModelicaDAE,
77
ModelicaFlattener,
88
ModelicaLinter,
99
ModelicaSimulator,
10+
buildFmuArchive,
1011
generateFmu,
1112
} from "@modelscript/core";
1213
import Modelica from "@modelscript/tree-sitter-modelica";
@@ -27,12 +28,16 @@ interface ExportFmuArgs {
2728
stopTime?: number;
2829
"step-size"?: number;
2930
stepSize?: number;
31+
"xml-only"?: boolean;
32+
xmlOnly?: boolean;
33+
type?: string;
34+
source?: boolean;
3035
}
3136

3237
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
3338
export const ExportFmu: CommandModule<{}, ExportFmuArgs> = {
3439
command: "export-fmu <name> <paths..>",
35-
describe: "Export a Modelica model as an FMI 2.0 Co-Simulation FMU model description",
40+
describe: "Export a Modelica model as an FMI 2.0 FMU (Model Exchange & Co-Simulation)",
3641
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3742
builder: ((yargs: any) => {
3843
return yargs
@@ -49,7 +54,7 @@ export const ExportFmu: CommandModule<{}, ExportFmuArgs> = {
4954
})
5055
.option("output", {
5156
alias: "o",
52-
description: "output file path (default: <name>.fmu.xml)",
57+
description: "output file path (default: <name>.fmu or <name>.fmu.xml)",
5358
type: "string",
5459
})
5560
.option("description", {
@@ -67,6 +72,22 @@ export const ExportFmu: CommandModule<{}, ExportFmuArgs> = {
6772
.option("step-size", {
6873
description: "default experiment step size",
6974
type: "number",
75+
})
76+
.option("xml-only", {
77+
description: "output only the modelDescription.xml (no archive)",
78+
type: "boolean",
79+
default: false,
80+
})
81+
.option("type", {
82+
description: "FMU type: 'me' (Model Exchange), 'cs' (Co-Simulation), 'both' (default)",
83+
type: "string",
84+
default: "both",
85+
choices: ["me", "cs", "both"],
86+
})
87+
.option("source", {
88+
description: "include C source files in the FMU archive",
89+
type: "boolean",
90+
default: true,
7091
});
7192
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7293
}) as any,
@@ -138,26 +159,70 @@ export const ExportFmu: CommandModule<{}, ExportFmuArgs> = {
138159
const simulator = new ModelicaSimulator(dae);
139160
simulator.prepare();
140161

141-
// Generate FMU model description
142-
const fmuOptions: FmuOptions = {
143-
modelIdentifier: args.name.replace(/\./g, "_"),
144-
description: args.description,
145-
generationTool: "ModelScript CLI",
146-
startTime: args.startTime ?? args["start-time"],
147-
stopTime: args.stopTime ?? args["stop-time"],
148-
stepSize: args.stepSize ?? args["step-size"],
162+
// FMU type flags
163+
const fmuType = {
164+
modelExchange: args.type === "me" || args.type === "both",
165+
coSimulation: args.type === "cs" || args.type === "both",
149166
};
150167

151-
const result = generateFmu(dae, fmuOptions, simulator.stateVars);
168+
const modelIdentifier = args.name.replace(/\./g, "_");
169+
170+
if (args.xmlOnly || args["xml-only"]) {
171+
// ── XML-only mode (original behavior) ──
172+
const result = generateFmu(
173+
dae,
174+
{
175+
modelIdentifier,
176+
description: args.description,
177+
generationTool: "ModelScript CLI",
178+
startTime: args.startTime ?? args["start-time"],
179+
stopTime: args.stopTime ?? args["stop-time"],
180+
stepSize: args.stepSize ?? args["step-size"],
181+
fmuType,
182+
},
183+
simulator.stateVars,
184+
);
185+
186+
const outputPath = args.output ?? `${modelIdentifier}.fmu.xml`;
187+
fs.writeFileSync(outputPath, result.modelDescriptionXml, "utf-8");
152188

153-
// Write output
154-
const outputPath = args.output ?? `${args.name.replace(/\./g, "_")}.fmu.xml`;
155-
fs.writeFileSync(outputPath, result.modelDescriptionXml, "utf-8");
189+
console.log(`FMU model description written to: ${outputPath}`);
190+
console.log(` Variables: ${result.scalarVariables.length}`);
191+
console.log(` Outputs: ${result.modelStructure.outputs.length}`);
192+
console.log(` Derivatives: ${result.modelStructure.derivatives.length}`);
193+
console.log(` Initial unknowns: ${result.modelStructure.initialUnknowns.length}`);
194+
} else {
195+
// ── Full FMU archive mode ──
196+
const archiveOptions: FmuArchiveOptions = {
197+
modelIdentifier,
198+
description: args.description,
199+
generationTool: "ModelScript CLI",
200+
startTime: args.startTime ?? args["start-time"],
201+
stopTime: args.stopTime ?? args["stop-time"],
202+
stepSize: args.stepSize ?? args["step-size"],
203+
fmuType,
204+
includeSources: args.source !== false,
205+
includeModelJson: true,
206+
};
156207

157-
console.log(`FMU model description written to: ${outputPath}`);
158-
console.log(` Variables: ${result.scalarVariables.length}`);
159-
console.log(` Outputs: ${result.modelStructure.outputs.length}`);
160-
console.log(` Derivatives: ${result.modelStructure.derivatives.length}`);
161-
console.log(` Initial unknowns: ${result.modelStructure.initialUnknowns.length}`);
208+
const result = buildFmuArchive(dae, archiveOptions, simulator);
209+
const outputPath = args.output ?? `${modelIdentifier}.fmu`;
210+
211+
fs.writeFileSync(outputPath, result.archive);
212+
213+
const types = [];
214+
if (fmuType.modelExchange) types.push("Model Exchange");
215+
if (fmuType.coSimulation) types.push("Co-Simulation");
216+
217+
console.log(`FMU archive written to: ${outputPath}`);
218+
console.log(` Type: ${types.join(" + ")}`);
219+
console.log(` GUID: ${result.fmuResult.guid}`);
220+
console.log(` Variables: ${result.fmuResult.scalarVariables.length}`);
221+
console.log(` States: ${result.fmuResult.modelStructure.derivatives.length}`);
222+
console.log(` Files: ${result.files.length}`);
223+
for (const f of result.files) {
224+
console.log(` ${f}`);
225+
}
226+
}
162227
},
163228
};

0 commit comments

Comments
 (0)