All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Refactored GCodePreview component to integrate new tracking logic, allowing for smoother motion representation based on live printer state. - Introduced sampleTime and updateIntervalMs props to manage motion updates more effectively. - Updated tracking.js to include new functions for handling sample intervals and refining segment positions. - Enhanced segmentStore and parser.worker.js to accommodate new segmentTime data for better performance tracking. - Improved PrinterGCodeFilePanel to utilize the new tracking state, ensuring accurate representation of gcode and live positions during printing.
182 lines
5.0 KiB
JavaScript
182 lines
5.0 KiB
JavaScript
// Structure-of-arrays storage for parsed G-code segments.
|
|
//
|
|
// Segment i runs from positions[i] to positions[i + 1], so the path is a
|
|
// single shared polyline and every other field is one entry per segment. The
|
|
// same class is used by the parser worker (filling it) and the main thread
|
|
// (receiving transferred chunks and feeding GPU buffers).
|
|
|
|
export const SEGMENT_FIELDS = [
|
|
['type', Uint8Array],
|
|
['tool', Uint8Array],
|
|
['fan', Uint8Array],
|
|
['layer', Uint16Array],
|
|
['temp', Uint16Array],
|
|
['width', Float32Array],
|
|
['height', Float32Array],
|
|
['feed', Float32Array],
|
|
['accel', Float32Array],
|
|
['offset', Uint32Array]
|
|
]
|
|
|
|
// Filled after the planner pass, not part of streamed chunks.
|
|
export const PLANNER_FIELDS = [
|
|
['actualSpeed', Float32Array],
|
|
['segmentTime', Float32Array],
|
|
['layerTime', Float32Array]
|
|
]
|
|
|
|
const ALL_FIELDS = [...SEGMENT_FIELDS, ...PLANNER_FIELDS]
|
|
|
|
export default class SegmentStore {
|
|
constructor(capacity = 1024) {
|
|
this.count = 0
|
|
this.capacity = 0
|
|
// Bumped whenever the backing arrays are reallocated so GPU-side
|
|
// consumers know to rebuild their attribute bindings.
|
|
this.generation = 0
|
|
this.positions = null
|
|
this.plannerReady = false
|
|
this.allocate(Math.max(16, capacity | 0))
|
|
}
|
|
|
|
allocate(capacity) {
|
|
const oldCount = this.count
|
|
const oldPositions = this.positions
|
|
const nextPositions = new Float32Array((capacity + 1) * 3)
|
|
if (oldPositions) {
|
|
nextPositions.set(oldPositions.subarray(0, (oldCount + 1) * 3))
|
|
}
|
|
this.positions = nextPositions
|
|
for (const [name, Type] of ALL_FIELDS) {
|
|
const next = new Type(capacity)
|
|
if (this[name]) next.set(this[name].subarray(0, oldCount))
|
|
this[name] = next
|
|
}
|
|
this.capacity = capacity
|
|
this.generation++
|
|
}
|
|
|
|
ensureCapacity(required) {
|
|
if (required <= this.capacity) return
|
|
let next = this.capacity
|
|
while (next < required) next = Math.ceil(next * 1.5) + 1024
|
|
this.allocate(next)
|
|
}
|
|
|
|
// Worker side: append one segment. The start point is written here and the
|
|
// end point is whatever the next push (or setEnd) writes.
|
|
push(
|
|
x,
|
|
y,
|
|
z,
|
|
type,
|
|
tool,
|
|
fan,
|
|
layer,
|
|
temp,
|
|
width,
|
|
height,
|
|
feed,
|
|
accel,
|
|
offset
|
|
) {
|
|
const i = this.count
|
|
if (i + 1 > this.capacity) this.ensureCapacity(i + 1)
|
|
const p = i * 3
|
|
this.positions[p] = x
|
|
this.positions[p + 1] = y
|
|
this.positions[p + 2] = z
|
|
this.type[i] = type
|
|
this.tool[i] = tool
|
|
this.fan[i] = fan
|
|
this.layer[i] = layer > 65535 ? 65535 : layer
|
|
this.temp[i] = temp < 0 ? 0 : temp > 65535 ? 65535 : temp
|
|
this.width[i] = width
|
|
this.height[i] = height
|
|
this.feed[i] = feed
|
|
this.accel[i] = accel
|
|
this.offset[i] = offset
|
|
this.count = i + 1
|
|
return i
|
|
}
|
|
|
|
setEnd(x, y, z) {
|
|
const p = this.count * 3
|
|
this.positions[p] = x
|
|
this.positions[p + 1] = y
|
|
this.positions[p + 2] = z
|
|
}
|
|
|
|
// Worker side: copy segments [from, to) into transferable buffers.
|
|
sliceChunk(from, to) {
|
|
const count = to - from
|
|
const chunk = { start: from, count }
|
|
const transfer = []
|
|
// Start points for every segment in the range plus the end of the last
|
|
// one, which is just the next start (or the final end point).
|
|
const positions = this.positions.slice(from * 3, (to + 1) * 3)
|
|
chunk.positions = positions
|
|
transfer.push(positions.buffer)
|
|
for (const [name] of SEGMENT_FIELDS) {
|
|
const copy = this[name].slice(from, to)
|
|
chunk[name] = copy
|
|
transfer.push(copy.buffer)
|
|
}
|
|
return { chunk, transfer }
|
|
}
|
|
|
|
// Main thread side: append a chunk produced by sliceChunk.
|
|
appendChunk(chunk) {
|
|
const { start, count } = chunk
|
|
if (start !== this.count) {
|
|
throw new Error(
|
|
`SegmentStore chunk out of order (expected ${this.count}, got ${start})`
|
|
)
|
|
}
|
|
this.ensureCapacity(start + count)
|
|
this.positions.set(chunk.positions, start * 3)
|
|
for (const [name] of SEGMENT_FIELDS) {
|
|
this[name].set(chunk[name], start)
|
|
}
|
|
this.count = start + count
|
|
}
|
|
|
|
applyPlanner({ actualSpeed, segmentTime, layerTime }) {
|
|
if (actualSpeed) this.actualSpeed.set(actualSpeed.subarray(0, this.count))
|
|
if (segmentTime) this.segmentTime.set(segmentTime.subarray(0, this.count))
|
|
if (layerTime) this.layerTime.set(layerTime.subarray(0, this.count))
|
|
this.plannerReady = true
|
|
}
|
|
|
|
getStart(i, out) {
|
|
const p = i * 3
|
|
out[0] = this.positions[p]
|
|
out[1] = this.positions[p + 1]
|
|
out[2] = this.positions[p + 2]
|
|
return out
|
|
}
|
|
|
|
getEnd(i, out) {
|
|
return this.getStart(i + 1, out)
|
|
}
|
|
|
|
// Index of the last segment whose source line starts at or before the given
|
|
// byte offset, or -1 when the offset precedes every segment.
|
|
segmentAtByteOffset(byteOffset) {
|
|
const offsets = this.offset
|
|
let lo = 0
|
|
let hi = this.count - 1
|
|
let result = -1
|
|
while (lo <= hi) {
|
|
const mid = (lo + hi) >>> 1
|
|
if (offsets[mid] <= byteOffset) {
|
|
result = mid
|
|
lo = mid + 1
|
|
} else {
|
|
hi = mid - 1
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|