GCodeReader

Introduction

vtkGCodeReader is a source object that reads a GCODE file.

Methods

extend

Method used to decorate a given object (publicAPI+model) with vtkGCodeReader characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IGCodeReaderInitialValues No (default: {})

getBaseURL

getDataAccessHelper

getUrl

Get the url of the object to load.

loadData

Load the object data.

Argument Type Required Description
options IGCodeReaderOptions No

newInstance

Method used to create a new instance of vtkGCodeReader

Argument Type Required Description
initialValues IGCodeReaderInitialValues No for pre-setting some of its content

parse

Parse data.

Argument Type Required Description
content String or ArrayBuffer Yes The content to parse.

parseAsArrayBuffer

Parse data as ArrayBuffer.

Argument Type Required Description
content ArrayBuffer Yes The content to parse.

parseAsText

Parse data as text.

Argument Type Required Description
content String Yes The content to parse.

requestData

Argument Type Required Description
inData Yes
outData Yes

setDataAccessHelper

Argument Type Required Description
dataAccessHelper Yes

setUrl

Set the url of the object to load.

Argument Type Required Description
url String Yes the url of the object to load.
option IGCodeReaderOptions No The PLY reader options.

Source

index.d.ts
import { vtkAlgorithm, vtkObject } from '../../../interfaces';
import HtmlDataAccessHelper from '../../Core/DataAccessHelper/HtmlDataAccessHelper';
import HttpDataAccessHelper from '../../Core/DataAccessHelper/HttpDataAccessHelper';
import JSZipDataAccessHelper from '../../Core/DataAccessHelper/JSZipDataAccessHelper';
import LiteHttpDataAccessHelper from '../../Core/DataAccessHelper/LiteHttpDataAccessHelper';

interface IGCodeReaderOptions {
binary?: boolean;
compression?: string;
progressCallback?: any;
}

/**
*
*/
export interface IGCodeReaderInitialValues {}

type vtkGCodeReaderBase = vtkObject &
Omit<
vtkAlgorithm,
| 'getInputData'
| 'setInputData'
| 'setInputConnection'
| 'getInputConnection'
| 'addInputConnection'
| 'addInputData'
>;

export interface vtkGCodeReader extends vtkGCodeReaderBase {
/**
*
*/
getBaseURL(): string;

/**
*
*/
getDataAccessHelper():
| HtmlDataAccessHelper
| HttpDataAccessHelper
| JSZipDataAccessHelper
| LiteHttpDataAccessHelper;

/**
* Get the url of the object to load.
*/
getUrl(): string;

/**
* Load the object data.
* @param {IGCodeReaderOptions} [options]
*/
loadData(options?: IGCodeReaderOptions): Promise<any>;

/**
* Parse data.
* @param {String | ArrayBuffer} content The content to parse.
*/
parse(content: string | ArrayBuffer): void;

/**
* Parse data as ArrayBuffer.
* @param {ArrayBuffer} content The content to parse.
*/
parseAsArrayBuffer(content: ArrayBuffer): void;

/**
* Parse data as text.
* @param {String} content The content to parse.
*/
parseAsText(content: string): void;

/**
*
* @param inData
* @param outData
*/
requestData(inData: any, outData: any): void;

/**
*
* @param dataAccessHelper
*/
setDataAccessHelper(
dataAccessHelper:
| HtmlDataAccessHelper
| HttpDataAccessHelper
| JSZipDataAccessHelper
| LiteHttpDataAccessHelper
): boolean;

/**
* Set the url of the object to load.
* @param {String} url the url of the object to load.
* @param {IGCodeReaderOptions} [option] The PLY reader options.
*/
setUrl(url: string, option?: IGCodeReaderOptions): Promise<string | any>;
}

/**
* Method used to decorate a given object (publicAPI+model) with vtkGCodeReader characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IGCodeReaderInitialValues} [initialValues] (default: {})
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: IGCodeReaderInitialValues
): void;

/**
* Method used to create a new instance of vtkGCodeReader
* @param {IGCodeReaderInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(
initialValues?: IGCodeReaderInitialValues
): vtkGCodeReader;

/**
* vtkGCodeReader is a source object that reads a GCODE file.
*/
export declare const vtkGCodeReader: {
newInstance: typeof newInstance;
extend: typeof extend;
};
export default vtkGCodeReader;
index.js
import macro from 'vtk.js/Sources/macros';
import BinaryHelper from 'vtk.js/Sources/IO/Core/BinaryHelper';
import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray';

// Enable several sources for DataAccessHelper
import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper'; // Just need HTTP
// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper'; // HTTP + gz
// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; // html + base64 + zip
// import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; // zip

// ----------------------------------------------------------------------------
// vtkGCodeReader methods
// ----------------------------------------------------------------------------
function vtkGCodeReader(publicAPI, model) {
const state = {
currentPosition: { x: 0, y: 0, z: 0 },
offset: { x: 0, y: 0, z: 0 },
currentLayer: 0,
layers: new Map(), // Map to store layer data
isAbsolute: true, // G90 is default
isMetric: true, // G21 is default
lastZ: 0, // Track Z changes for layer detection
};

model.classHierarchy.push('vtkGCodeReader');

if (!model.dataAccessHelper) {
model.dataAccessHelper = DataAccessHelper.get('http');
}

function fetchData(url, option = {}) {
const { compression, progressCallback } = model;
if (option.binary) {
return model.dataAccessHelper.fetchBinary(url, {
compression,
progressCallback,
});
}
return model.dataAccessHelper.fetchText(publicAPI, url, {
compression,
progressCallback,
});
}

function detectLayerChange(newZ) {
if (Math.abs(newZ - state.lastZ) > 0.001) {
state.currentLayer++;
state.lastZ = newZ;
return true;
}
return false;
}

function initializeLayer() {
if (!state.layers.has(state.currentLayer)) {
const points = vtkPoints.newInstance();
const lines = vtkCellArray.newInstance();
const polyData = vtkPolyData.newInstance();

polyData.setPoints(points);
polyData.setLines(lines);

state.layers.set(state.currentLayer, {
polyData,
points,
lines,
zHeight: state.lastZ,
});
}
}

function addLineToLayer(startPoint, endPoint) {
initializeLayer();
const layer = state.layers.get(state.currentLayer);

// Add points and get their indices
const startIndex = layer.points.insertNextPoint(
startPoint[0],
startPoint[1],
startPoint[2]
);
const endIndex = layer.points.insertNextPoint(
endPoint[0],
endPoint[1],
endPoint[2]
);

// Add line cell
layer.lines.insertNextCell([startIndex, endIndex]);
}

function processMove(params) {
const newPosition = { ...state.currentPosition };
let positionChanged = false;

['X', 'Y', 'Z'].forEach((axis) => {
if (axis in params) {
const value = state.isMetric ? params[axis] : params[axis] * 25.4;
newPosition[axis.toLowerCase()] = state.isAbsolute
? value + state.offset[axis.toLowerCase()]
: state.currentPosition[axis.toLowerCase()] + value;
positionChanged = true;
}
});

if (positionChanged) {
if ('Z' in params) {
detectLayerChange(newPosition.z);
}

const startPoint = [
state.currentPosition.x,
state.currentPosition.y,
state.currentPosition.z,
];
const endPoint = [newPosition.x, newPosition.y, newPosition.z];

addLineToLayer(startPoint, endPoint);
state.currentPosition = newPosition;
}
}

function processG92(params) {
['X', 'Y', 'Z'].forEach((axis) => {
if (axis in params) {
state.offset[axis.toLowerCase()] =
state.currentPosition[axis.toLowerCase()] -
(state.isMetric ? params[axis] : params[axis] * 25.4);
}
});
}

function processCommand(command, params) {
switch (command) {
case 'G0': // Rapid move
case 'G1': // Linear move
processMove(params);
break;
case 'G20': // Imperial
state.isMetric = false;
break;
case 'G21': // Metric
state.isMetric = true;
break;
case 'G90': // Absolute positioning
state.isAbsolute = true;
break;
case 'G91': // Relative positioning
state.isAbsolute = false;
break;
case 'G92': // Set position
processG92(params);
break;
default:
break;
}
}

function parseGCode(gcodeText) {
const lines = gcodeText.split('\n');

lines.forEach((line) => {
const sline = line.split(';')[0].trim();
if (!sline) return;

const tokens = sline.split(' ');
const command = tokens[0];

const params = {};
tokens.slice(1).forEach((token) => {
const param = token[0];
const value = parseFloat(token.slice(1));
if (!Number.isNaN(value)) {
params[param] = value;
}
});

processCommand(command, params);
});
}

// Public methods
publicAPI.setUrl = (url, option = { binary: true }) => {
model.url = url;
const path = url.split('/');
path.pop();
model.baseURL = path.join('/');
model.compression = option.compression;

return publicAPI.loadData({
progressCallback: option.progressCallback,
binary: !!option.binary,
});
};

publicAPI.loadData = (option = {}) => {
const promise = fetchData(model.url, option);
promise.then(publicAPI.parse);
return promise;
};

publicAPI.parseAsText = (content) => {
parseGCode(content);
};

publicAPI.parseAsArrayBuffer = (content) => {
const data = BinaryHelper.arrayBufferToString(content);
parseGCode(data);
};

publicAPI.parse = (content) => {
if (typeof content === 'string') {
publicAPI.parseAsText(content);
} else {
publicAPI.parseAsArrayBuffer(content);
}

state.layers.forEach((layer, i) => {
model.output[i] = layer.polyData;
});
};

publicAPI.requestData = (inData, outData) => {
publicAPI.parse(model.parseData);
};

publicAPI.getNumberOfOutputPorts = () => state.layers.size;
}

const DEFAULT_VALUES = {
// baseURL: null,
// dataAccessHelper: null,
// url: null,
};

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Build VTK API
macro.obj(publicAPI, model);
macro.get(publicAPI, model, ['url', 'baseURL']);
macro.setGet(publicAPI, model, ['dataAccessHelper']);
macro.algo(publicAPI, model, 0, 1);
macro.event(publicAPI, model, 'ready');

vtkGCodeReader(publicAPI, model);
}

export const newInstance = macro.newInstance(extend, 'vtkGCodeReader');

export default {
extend,
newInstance,
};