Upload project files
This commit is contained in:
173
kdl-wasm/web/src/olp/grl.ts
Normal file
173
kdl-wasm/web/src/olp/grl.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user