174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
import {
|
|
generateGrlProgram,
|
|
type GeneratedGrlProgram,
|
|
type GeneratedPathPointSpec,
|
|
type GrlGeneratorStyle,
|
|
type GrlProgramGenerationSpec
|
|
} from "../grl/generator/index.js";
|
|
import type {
|
|
OlpOperation,
|
|
OlpPath,
|
|
OlpPathPoint,
|
|
OlpProgram,
|
|
OlpProjectModel,
|
|
OlpSpeed,
|
|
OlpTarget,
|
|
OlpZone
|
|
} from "./model.js";
|
|
import { validateOlpProject } from "./model.js";
|
|
|
|
export interface PathOperationGrlOptions {
|
|
moduleName?: string;
|
|
programId?: string;
|
|
style?: GrlGeneratorStyle;
|
|
}
|
|
|
|
export interface PathOperationGrlResult extends GeneratedGrlProgram {
|
|
spec: GrlProgramGenerationSpec;
|
|
program: OlpProgram;
|
|
}
|
|
|
|
export function pathOperationToGrl(
|
|
model: OlpProjectModel,
|
|
operationId: string,
|
|
options: PathOperationGrlOptions = {}
|
|
): PathOperationGrlResult {
|
|
const validation = validateOlpProject(model);
|
|
const errors = validation.issues.filter((issue) => issue.severity === "error");
|
|
if (errors.length > 0) {
|
|
throw new Error(`Cannot generate GRL from invalid OLP model: ${errors.map((issue) => issue.code).join(", ")}`);
|
|
}
|
|
|
|
const operation = required(model.operations.find((item) => item.id === operationId), `Operation ${operationId} not found`);
|
|
const path = required(model.paths.find((item) => item.id === operation.pathId), `Path ${operation.pathId} not found`);
|
|
const targets = collectTargetsForPath(model.targets, path);
|
|
const spec = toGenerationSpec(model, operation, path, targets, options.moduleName);
|
|
const generated = generateGrlProgram(spec, options.style ?? "expanded");
|
|
const program: OlpProgram = {
|
|
id: options.programId ?? `${operation.id}_grl`,
|
|
name: spec.moduleName,
|
|
language: "grl",
|
|
entryOperationIds: [operation.id],
|
|
source: {
|
|
kind: "generated",
|
|
text: generated.text,
|
|
generatedFrom: {
|
|
operationId: operation.id,
|
|
pathId: path.id
|
|
}
|
|
}
|
|
};
|
|
|
|
return {
|
|
...generated,
|
|
spec,
|
|
program
|
|
};
|
|
}
|
|
|
|
function toGenerationSpec(
|
|
model: OlpProjectModel,
|
|
operation: OlpOperation,
|
|
path: OlpPath,
|
|
targets: OlpTarget[],
|
|
moduleName?: string
|
|
): GrlProgramGenerationSpec {
|
|
return {
|
|
moduleName: moduleName ?? sanitizeIdentifier(model.project.name),
|
|
speeds: Object.fromEntries(model.speeds.map((speed) => [speed.name, renderSpeed(speed)])),
|
|
zones: Object.fromEntries(model.zones.map((zone) => [zone.name, renderZone(zone)])),
|
|
targets: targets.map((target) => {
|
|
if (target.kind === "joint") {
|
|
return {
|
|
name: target.name,
|
|
kind: "joint" as const,
|
|
values: target.joints ?? []
|
|
};
|
|
}
|
|
return {
|
|
name: target.name,
|
|
kind: "pose" as const,
|
|
values: target.pose ?? [0, 0, 0, 0, 0, 0]
|
|
};
|
|
}),
|
|
path: {
|
|
name: path.name,
|
|
...(path.source ? { source: path.source } : {}),
|
|
defaults: {
|
|
speed: resolveSpeedName(model, path.defaults?.speedId) ?? "v_linear",
|
|
zone: resolveZoneName(model, path.defaults?.zoneId) ?? "z10"
|
|
},
|
|
points: path.points.map((point) => toGeneratedPoint(model, point))
|
|
},
|
|
operation: {
|
|
name: operation.name,
|
|
kind: operation.kind,
|
|
path: path.name,
|
|
...(operation.startAction ? { startAction: operation.startAction } : {}),
|
|
...(operation.endAction ? { endAction: operation.endAction } : {})
|
|
}
|
|
};
|
|
}
|
|
|
|
function collectTargetsForPath(allTargets: OlpTarget[], path: OlpPath): OlpTarget[] {
|
|
const targetIds = new Set(path.points.flatMap((point) => [point.targetId, point.viaTargetId].filter((id): id is string => Boolean(id))));
|
|
return allTargets.filter((target) => targetIds.has(target.id));
|
|
}
|
|
|
|
function toGeneratedPoint(model: OlpProjectModel, point: OlpPathPoint): GeneratedPathPointSpec {
|
|
const target = required(model.targets.find((item) => item.id === point.targetId), `Target ${point.targetId} not found`);
|
|
const via = point.viaTargetId
|
|
? required(model.targets.find((item) => item.id === point.viaTargetId), `Via target ${point.viaTargetId} not found`)
|
|
: undefined;
|
|
return {
|
|
id: point.name,
|
|
motion: point.motion,
|
|
target: target.name,
|
|
...(via ? { via: via.name } : {}),
|
|
...(point.speedId ? { speed: required(resolveSpeedName(model, point.speedId), `Speed ${point.speedId} not found`) } : {}),
|
|
...(point.zoneId ? { zone: required(resolveZoneName(model, point.zoneId), `Zone ${point.zoneId} not found`) } : {})
|
|
};
|
|
}
|
|
|
|
function renderSpeed(speed: OlpSpeed): string {
|
|
if (speed.kind === "joint_percent") {
|
|
return `joint(${speed.value} %)`;
|
|
}
|
|
return `linear(${speed.value} mm/s)`;
|
|
}
|
|
|
|
function renderZone(zone: OlpZone): string {
|
|
if (zone.kind === "fine") {
|
|
return "fine";
|
|
}
|
|
if (zone.kind === "cnt") {
|
|
return `z(${zone.value ?? 10})`;
|
|
}
|
|
return `z(${zone.value ?? 10} mm)`;
|
|
}
|
|
|
|
function resolveSpeedName(model: OlpProjectModel, id?: string): string | undefined {
|
|
if (!id) return undefined;
|
|
return model.speeds.find((speed) => speed.id === id)?.name;
|
|
}
|
|
|
|
function resolveZoneName(model: OlpProjectModel, id?: string): string | undefined {
|
|
if (!id) return undefined;
|
|
return model.zones.find((zone) => zone.id === id)?.name;
|
|
}
|
|
|
|
function sanitizeIdentifier(value: string): string {
|
|
const sanitized = value.replace(/[^A-Za-z0-9_]/g, "_");
|
|
if (/^[A-Za-z_]/.test(sanitized)) {
|
|
return sanitized;
|
|
}
|
|
return `Project_${sanitized}`;
|
|
}
|
|
|
|
function required<T>(value: T | undefined, message: string): T {
|
|
if (value === undefined) {
|
|
throw new Error(message);
|
|
}
|
|
return value;
|
|
}
|