Tom Butcher b3e366e813
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Remove gcode-preview dependency and related code
- Deleted the gcode-preview package from package.json and pnpm-lock.yaml.
- Removed references to gcode-preview in vite.config.js.
- Introduced new GCodePreview component and related files for G-code visualization.
- Added GCodeViewer components including Bed, HotEnd, and SegmentMesh for rendering.
- Implemented color modes and feature types for enhanced G-code rendering.
- Added GCodeLegend for displaying feature information and toggling visibility.
- Established GCodeScene for managing the 3D rendering context and controls.
2026-11-08 18:08:12 +00:00

170 lines
5.3 KiB
JavaScript

import { FEATURE } from './featureTypes.js'
// Simplified Klipper-style motion planner. For every segment it computes the
// trapezoid the firmware would run given the requested feedrate, the active
// acceleration and the junction limits between consecutive moves, which yields
// the speed the toolhead actually reaches and how long each segment takes.
//
// It ignores extruder-specific limits, minimum cruise ratio smoothing and
// input shaper, so times are approximate but relative values are faithful.
const SQRT2_MINUS_1 = Math.SQRT2 - 1
export function planMotion(store, options = {}) {
const {
maxVelocity = 500,
squareCornerVelocity = 5,
defaultAccel = 3000
} = options
const n = store.count
const positions = store.positions
const feed = store.feed
const accelField = store.accel
const type = store.type
const distance = new Float32Array(n)
const deltaV2 = new Float32Array(n)
const maxStartV2 = new Float32Array(n)
const maxCruiseV2 = new Float32Array(n)
const startV2 = new Float32Array(n)
const actualSpeed = new Float32Array(n)
const segmentTime = new Float32Array(n)
const scv2 = squareCornerVelocity * squareCornerVelocity
let prevValid = false
let prevDx = 0
let prevDy = 0
let prevDz = 0
let prevAccel = defaultAccel
let prevJunctionDeviation = 0
for (let i = 0; i < n; i++) {
const p = i * 3
const dx = positions[p + 3] - positions[p]
const dy = positions[p + 4] - positions[p + 1]
const dz = positions[p + 5] - positions[p + 2]
const d = Math.sqrt(dx * dx + dy * dy + dz * dz)
distance[i] = d
if (type[i] === FEATURE.NONE || d <= 0) {
// Position jump: no motion, acts as a full stop.
deltaV2[i] = 0
maxStartV2[i] = 0
maxCruiseV2[i] = 0
prevValid = false
continue
}
let accel = accelField[i]
if (!(accel > 0)) accel = defaultAccel
let vReq = feed[i]
if (!(vReq > 0)) vReq = maxVelocity
if (vReq > maxVelocity) vReq = maxVelocity
const cruiseV2 = vReq * vReq
maxCruiseV2[i] = cruiseV2
deltaV2[i] = 2 * d * accel
const junctionDeviation = (scv2 * SQRT2_MINUS_1) / accel
const ux = dx / d
const uy = dy / d
const uz = dz / d
if (!prevValid) {
maxStartV2[i] = 0
} else {
let cosTheta = -(ux * prevDx + uy * prevDy + uz * prevDz)
if (cosTheta > 0.999999) {
maxStartV2[i] = 0
} else {
if (cosTheta < -0.999999) cosTheta = -0.999999
const sinThetaD2 = Math.sqrt(0.5 * (1 - cosTheta))
const rJd = sinThetaD2 / (1 - sinThetaD2)
let limit = rJd * junctionDeviation * accel
const prevLimit = rJd * prevJunctionDeviation * prevAccel
if (prevLimit < limit) limit = prevLimit
if (cruiseV2 < limit) limit = cruiseV2
const prevCruise = maxCruiseV2[i - 1]
if (prevCruise < limit) limit = prevCruise
const reachable = maxStartV2[i - 1] + deltaV2[i - 1]
if (reachable < limit) limit = reachable
maxStartV2[i] = limit
}
}
prevValid = true
prevDx = ux
prevDy = uy
prevDz = uz
prevAccel = accel
prevJunctionDeviation = junctionDeviation
}
// Backward pass: nothing may end faster than it can decelerate into the
// next junction (the file ends at rest).
let nextStartV2 = 0
for (let i = n - 1; i >= 0; i--) {
const reachable = nextStartV2 + deltaV2[i]
const v2 = maxStartV2[i] < reachable ? maxStartV2[i] : reachable
startV2[i] = v2
nextStartV2 = v2
}
// Forward pass and trapezoid generation.
let currentStartV2 = 0
for (let i = 0; i < n; i++) {
const d = distance[i]
if (type[i] === FEATURE.NONE || d <= 0) {
currentStartV2 = 0
continue
}
let sV2 = startV2[i] < currentStartV2 ? startV2[i] : currentStartV2
const nextS = i + 1 < n ? startV2[i + 1] : 0
const reachableEnd = sV2 + deltaV2[i]
let eV2 = nextS < reachableEnd ? nextS : reachableEnd
if (eV2 < 0) eV2 = 0
let cV2 = (sV2 + eV2 + deltaV2[i]) * 0.5
if (cV2 > maxCruiseV2[i]) cV2 = maxCruiseV2[i]
if (cV2 < sV2) cV2 = sV2
if (cV2 < eV2) cV2 = eV2
let accel = accelField[i]
if (!(accel > 0)) accel = defaultAccel
const sV = Math.sqrt(sV2)
const eV = Math.sqrt(eV2)
const cV = Math.sqrt(cV2)
const halfInvAccel = 0.5 / accel
const accelD = (cV2 - sV2) * halfInvAccel
const decelD = (cV2 - eV2) * halfInvAccel
let cruiseD = d - accelD - decelD
if (cruiseD < 0) cruiseD = 0
const t =
(cV - sV) / accel + (cV > 0 ? cruiseD / cV : 0) + (cV - eV) / accel
actualSpeed[i] = cV
segmentTime[i] = t
currentStartV2 = eV2
}
return { actualSpeed, segmentTime }
}
// Sums segment times per layer and expands them back to one value per
// segment so the shader can colour by layer time with a plain attribute.
export function computeLayerTimes(store, segmentTime, layers) {
const n = store.count
const layerTime = new Float32Array(n)
const perLayer = new Float32Array(layers.length)
let total = 0
for (let l = 0; l < layers.length; l++) {
const { start, end } = layers[l]
let sum = 0
for (let i = start; i < end && i < n; i++) sum += segmentTime[i]
perLayer[l] = sum
total += sum
for (let i = start; i < end && i < n; i++) layerTime[i] = sum
}
return { layerTime, perLayer, total }
}