This repo contains set of tools for gcode post-processing, enabling you to easily create your own scripts. This script should be compatible with all slicers that support post-processing, however it was written for PrusaSlicer and was not tested in other slicers.
There are also some modules available out-of-the-box
-
Install LTS version of NodeJS
-
Clone this repo
-
Open Terminal in the cloned directory
-
Run
npm i
-
npm will automatically download all required dependencies, compile and create a 'proxy' script to run post-processing conveniently.
If nothing goes wrong, you will see message like this:
GCode post-processing script was successfully installed!
You can add the following command to the PrusaSlicer to use it:
/Users/Shared/PrusaSlicer-post-processing/v2/run-post-processing.cmd
To see available available arguments, run:
npm run start -- --help
- Copy provided command and insert it into slicer
- After the command insert module name you want to use and (optionally) additional module options. Example (single line):
/Users/Shared/PrusaSlicer-post-processing/v2/run-post-processing.cmd fan-optimizer
- Gcode Processor - wrapper for convenient gcode processing
- GCommand - set of GCode commands
- GCode - convenient gcode parsing/serializing
- Pipe - processes and modifies gcode
- Timeline - stores data in chronological order
- Toolhead - emulates toolhead and does very basic time approximation
- Fan - emulates part cooling fan
- Various helpers
Eliminates annoying "we-we-we-we-we" that fan makes because of PrusaSlicer's command spamming when dynamic overhang fan speed is enabled. It also features premature fan speedup that will give fan some time to speedup before overhang.
- Smoothing of rapid and frequent fan speed changes.
- Premature fan speedup to give fan some time to gain requested speed.
index.js fan-optimizer <filepath> [--smoothTime] [--smoothingResetThreshold] [--speedupTime]
filepath
Path to the file to process [required]
--smoothTime
[ms] Smooths frequent fan speed changes over specified time period. [default: 300]--smoothingResetThreshold
[mm] Reset fan smoothing if non-print move longer than this value was performed. [default: 20]--speedupTime
[ms] Activate fan N ms before to let it gain requested speed. [default: 500]
Modules are independent sub-programs that can be run from a single place (namely single post-processing script) implementing various features.
To begin with, create new folder for your module in src/modules
.
Create index.ts
file inside this folder, it will export your module's configuration.
You can refer to yargs official documentation
for more detailed documentation on modules
Define and export your module
// my-custom-module/index.ts
import yargs from 'yargs';
export const MyCustomModule: yargs.CommandModule = {};
Inside module provide command
entry and handler
function
// my-custom-module/index.ts
import yargs from 'yargs';
export const MyCustomModule: yargs.CommandModule = {
command: 'my-custom-module',
handler: async (args) => {},
};
Your module must accept path to the file as positional argument in order to modify gcode.
Let's create your module's args type which will contain only filepath for now
// my-custom-module/index.ts
import yargs from 'yargs';
type MyCustomModuleArgs = {
filepath: string;
};
export const MyCustomModule: yargs.CommandModule<{}, MyCustomModuleArgs> = {
command: 'my-custom-module',
handler: async (args) => {},
};
Optionally, you can add description to your module
// my-custom-module/index.ts
import yargs from 'yargs';
type MyCustomModuleArgs = {
filepath: string;
};
export const MyCustomModule: yargs.CommandModule<{}, MyCustomModuleArgs> = {
command: 'my-custom-module',
describe: 'My custom module',
handler: async (args) => {},
};
To let yargs know what arguments your module accepts, create builder function
You can utilize withFilepathValidator
helper function which will do path
validating for you
// my-custom-module/index.ts
import yargs from 'yargs';
import { withFilepathValidator } from '../../shared/helpers';
type MyCustomModuleArgs = {
filepath: string;
};
export const MyCustomModule: yargs.CommandModule<{}, MyCustomModuleArgs> = {
command: 'my-custom-module <filepath>',
describe: 'My custom module',
builder: (yargs) => {
const args = yargs.positional('filepath', {
description: 'Path to the file to process',
type: 'string',
});
return withFilepathValidator('filepath', args);
},
handler: async (args) => {},
};
Implement handler function utilizing neat GCodeProcessor
class
// my-custom-module/index.ts
import yargs from 'yargs';
import { GCodeProcessor } from '../../shared/gcode-processor/gcode-processor';
import { withFilepathValidator } from '../../shared/helpers';
type MyCustomModuleArgs = {
filepath: string;
};
export const MyCustomModule: yargs.CommandModule<{}, MyCustomModuleArgs> = {
command: 'my-custom-module <filepath>',
describe: 'My custom module',
builder: (yargs) => {
let args = yargs.positional('filepath', {
description: 'Path to the file to process',
type: 'string',
});
return withFilepathValidator('filepath', args);
},
handler: async (args) => {
const processor = new GCodeProcessor();
await processor.run(args.filepath);
},
};
Currently, this module does't do much. It just forwards input to the output
without any modifications. Let's connect CustomPipe
that removes all the
comment-only lines from the file. See pipe chapter
// my-custom-module/index.ts
import yargs from 'yargs';
import { GCodeProcessor } from '../../shared/gcode-processor/gcode-processor';
import { withFilepathValidator } from '../../shared/helpers';
import { CustomPipe } from './pipes/custom.pipe';
type MyCustomModuleArgs = {
filepath: string;
};
export const MyCustomModule: yargs.CommandModule<{}, MyCustomModuleArgs> = {
command: 'my-custom-module <filepath>',
describe: 'My custom module',
builder: (yargs) => {
let args = yargs.positional('filepath', {
description: 'Path to the file to process',
type: 'string',
});
return withFilepathValidator('filepath', args);
},
handler: async (args) => {
const processor = new GCodeProcessor();
processor.addPipe(new CustomPipe());
await processor.run(args.filepath);
},
};
Now, build and test!
npm run build
npm run start -- my-custom-module ./test-gcode/3DBenchy.gcode
After running the script we can observe that all comment-only lines were removed
...
M107
- ;LAYER_CHANGE
- ;Z:0.25
- ;HEIGHT:0.25
G10 ; retract
G1 Z.25 F36000
G1 X79.8 Y97.769
G11 ; unretract
M204 S4000
- ;TYPE:Skirt/Brim
- ;WIDTH:0.6
G1 F3600
...
Pipes are essential part of any module. These reusable parts can be stacked together to achieve your desired behavior.
-
Every pipe must extend
Pipe
base class, as well as implementinput
method. Resulting data, after processing, should be forwarded intooutput
method. -
Pipes have list of their supported gcodes. To be precise, this list should include all gcode commands that pipe somehow relies on (f.e reads command's arguments).
If command is not on the list - only the command will be parsed (skipping any its arguments, etc...)
To add command to the list of supported - pipe should call
this.addSupportedGcodes
in its constructor -
To perform any prepare or finishing tasks, respective
onWarmup
andonCooldown
hooks are available. AfteronCooldown
method is called, pipe must output all the data it wants to be outputted.
In this example we will build CustomPipe
for our MyCustomModule
In the module's folder create pipes
folder to better organize code.
Inside his folder create custom.pipe.ts
file and export CustomPipe
class
// my-custom-module/pipes/custom.pipe.ts
import { Pipe } from '../../../shared/pipe';
export class CustomPipe extends Pipe {}
Every pipe must implement input
method, so add it
// my-custom-module/pipes/custom.pipe.ts
import { GCode } from '../../../shared/gcode';
import { Pipe } from '../../../shared/pipe';
export class CustomPipe extends Pipe {
input(gcode: GCode): void {}
}
Notify gcode processor that our pipe supports gcode comments
// my-custom-module/pipes/custom.pipe.ts
import { GCode, GCommand } from '../../../shared/gcode';
import { Pipe } from '../../../shared/pipe';
export class CustomPipe extends Pipe {
constructor() {
super();
this.addSupportedGcodes([GCommand.COMMENT]);
}
input(gcode: GCode): void {}
}
To remove all the comment-only lines from the input we can check if input command is a comment. If not - forward it to the output. Otherwise, do nothing (effectively removing it)
// my-custom-module/pipes/custom.pipe.ts
import { GCode, GCommand } from '../../../shared/gcode';
import { Pipe } from '../../../shared/pipe';
export class CustomPipe extends Pipe {
constructor() {
super();
this.addSupportedGcodes([GCommand.COMMENT]);
}
input(gcode: GCode): void {
if (gcode.command !== GCommand.COMMENT) {
this.output(gcode);
}
}
}