11/**
2- * Example showing how to slice an STL model with support structures
3- * This demonstrates the support generation functionality of Polyslice
4- * by loading an STL file from the resources folder
2+ * Example showing how to slice an "arch" using CSG (default) or an STL with support structures.
3+ * This demonstrates support generation with either a procedurally-built arch (box - cylinder)
4+ * or a provided STL. The default is the CSG arch.
55 *
66 * Usage:
7- * node examples/scripts/slice-arch.js
7+ * node examples/scripts/slice-arch.js # uses CSG arch (default)
8+ * node examples/scripts/slice-arch.js --use-stl # loads block.test.stl instead
89 *
9- * The example loads block.test.stl by default. You can modify the script
10- * to load strip.test.stl or other STL files with overhanging features.
10+ * You can change the STL path or CSG params below.
1111 */
1212
13- const { Polyslice, Printer, Filament } = require ( '../../src/index' ) ;
14- const fs = require ( 'fs' ) ;
15- const path = require ( 'path' ) ;
13+ const { Polyslice, Printer, Filament } = require ( "../../src/index" ) ;
14+ const fs = require ( "fs" ) ;
15+ const path = require ( "path" ) ;
16+ const THREE = require ( "three" ) ;
17+ const { Brush, Evaluator, SUBTRACTION } = require ( "three-bvh-csg" ) ;
18+
19+ // Export a mesh as an STL (binary) using three's STLExporter (ESM-only)
20+ async function exportMeshAsSTL ( object , outPath ) {
21+ const mod = await import ( "three/examples/jsm/exporters/STLExporter.js" ) ;
22+ const STLExporter = mod . STLExporter || mod . default ?. STLExporter || mod . default ;
23+ const exporter = new STLExporter ( ) ;
24+ const data = exporter . parse ( object , { binary : true } ) ;
25+
26+ let nodeBuffer ;
27+ if ( typeof data === "string" ) {
28+ nodeBuffer = Buffer . from ( data , "utf8" ) ;
29+ } else if ( ArrayBuffer . isView ( data ) ) {
30+ nodeBuffer = Buffer . from ( data . buffer , data . byteOffset || 0 , data . byteLength ) ;
31+ } else if ( data instanceof ArrayBuffer ) {
32+ nodeBuffer = Buffer . from ( data ) ;
33+ } else {
34+ throw new Error ( "Unexpected STLExporter output type" ) ;
35+ }
1636
17- console . log ( 'Polyslice Support Generation Example' ) ;
18- console . log ( '====================================\n' ) ;
37+ fs . writeFileSync ( outPath , nodeBuffer ) ;
38+ return outPath ;
39+ }
40+
41+ console . log ( "Polyslice Support Generation Example" ) ;
42+ console . log ( "====================================\n" ) ;
1943
2044// Create printer and filament configuration objects.
21- const printer = new Printer ( ' Ender5' ) ;
22- const filament = new Filament ( ' GenericPLA' ) ;
45+ const printer = new Printer ( " Ender5" ) ;
46+ const filament = new Filament ( " GenericPLA" ) ;
2347
24- console . log ( ' Printer & Filament Configuration:' ) ;
48+ console . log ( " Printer & Filament Configuration:" ) ;
2549console . log ( `- Printer: ${ printer . model } ` ) ;
2650console . log ( `- Build Volume: ${ printer . getSizeX ( ) } x${ printer . getSizeY ( ) } x${ printer . getSizeZ ( ) } mm` ) ;
2751console . log ( `- Filament: ${ filament . name } (${ filament . type . toUpperCase ( ) } )` ) ;
2852console . log ( `- Brand: ${ filament . brand } \n` ) ;
2953
30- // Main async function to load STL and slice
31- async function main ( ) {
32- // Load the test STL file with overhangs
33- const stlPath = path . join ( __dirname , '..' , '..' , 'resources' , 'support' , 'block.test.stl' ) ;
34-
35- console . log ( 'Loading STL file...' ) ;
36- console . log ( `- Path: ${ stlPath } \n` ) ;
54+ // Toggle: default to CSG arch; enable STL via flag/env
55+ const useStl = process . argv . includes ( "--use-stl" ) || process . env . ARCH_USE_STL === "1" ;
3756
38- let mesh ;
57+ // CSG Arch parameters
58+ const ARCH_WIDTH = 40 ; // X dimension (mm)
59+ const ARCH_HEIGHT = 10 ; // Y dimension (mm)
60+ const ARCH_THICKNESS = 20 ; // Z dimension (mm)
61+ const ARCH_RADIUS = 15 ; // Radius of semi-circular cut (mm)
3962
40- try {
41- // Load STL using three.js STLLoader with parse method
42- // This is more reliable in Node.js than using the URL-based load method
43- const THREE = require ( 'three' ) ;
44- const { STLLoader } = await import ( 'three/examples/jsm/loaders/STLLoader.js' ) ;
63+ // STL path (if --use-stl)
64+ const stlPath = path . join ( __dirname , ".." , ".." , "resources" , "support" , "block.test.stl" ) ;
4565
46- const buffer = fs . readFileSync ( stlPath ) ;
47- const loader = new STLLoader ( ) ;
48- const geometry = loader . parse ( buffer . buffer ) ;
49- // geometry.rotateX(Math.PI);
66+ /**
67+ * Build an arch by subtracting a horizontal cylinder (lying flat) from a box.
68+ * Box is centered at origin; cylinder axis along X (flat), offset +Y by half height to form a semi-circle cut.
69+ * Final mesh is lifted so the bottom sits on Z=0 for printing.
70+ */
71+ function createArchMesh ( width = ARCH_WIDTH , height = ARCH_HEIGHT , thickness = ARCH_THICKNESS , radius = ARCH_RADIUS ) {
72+
73+ // Base box
74+ const boxGeo = new THREE . BoxGeometry ( width , height , thickness ) ;
75+ const boxBrush = new Brush ( boxGeo ) ;
76+ boxBrush . updateMatrixWorld ( ) ;
77+
78+ // Cylinder lying flat along X: default cylinder axis is Y, rotate about Z by 90° -> axis becomes X
79+ const cylLength = width * 1.25 ; // ensure it spans the box width
80+ const cylGeo = new THREE . CylinderGeometry ( radius , radius , cylLength , 48 ) ;
81+ const cylBrush = new Brush ( cylGeo ) ;
82+ cylBrush . position . z = - height
83+ cylBrush . updateMatrixWorld ( ) ;
84+
85+ // Subtract cylinder from box to form arch opening
86+ const evalCSG = new Evaluator ( ) ;
87+ const resultBrush = evalCSG . evaluate ( boxBrush , cylBrush , SUBTRACTION ) ;
88+
89+ // Create mesh and place on build plate
90+ const mat = new THREE . MeshBasicMaterial ( ) ;
91+ const archMesh = new THREE . Mesh ( resultBrush . geometry , mat ) ;
92+ archMesh . position . set ( 0 , 0 , thickness / 2 ) ;
93+ archMesh . updateMatrixWorld ( ) ;
94+ return archMesh ;
95+ }
5096
51- // Create mesh with the loaded geometry
52- const material = new THREE . MeshPhongMaterial ( { color : 0x808080 , specular : 0x111111 , shininess : 200 } ) ;
53- mesh = new THREE . Mesh ( geometry , material ) ;
97+ // Main async function to create or load the model and slice
98+ async function main ( ) {
99+ let mesh ;
54100
55- console . log ( '✅ STL file loaded successfully' ) ;
101+ if ( useStl ) {
102+ console . log ( "Loading STL file..." ) ;
103+ console . log ( `- Path: ${ stlPath } \n` ) ;
104+ try {
105+ const { STLLoader } = await import ( "three/examples/jsm/loaders/STLLoader.js" ) ;
106+ const buffer = fs . readFileSync ( stlPath ) ;
107+ const loader = new STLLoader ( ) ;
108+ const geometry = loader . parse ( buffer . buffer ) ;
109+ const material = new THREE . MeshPhongMaterial ( { color : 0x808080 , specular : 0x111111 , shininess : 200 } ) ;
110+ mesh = new THREE . Mesh ( geometry , material ) ;
111+ console . log ( "✅ STL file loaded successfully" ) ;
112+ console . log ( `- Geometry type: ${ mesh . geometry . type } ` ) ;
113+ console . log ( `- Vertices: ${ mesh . geometry . attributes . position . count } ` ) ;
114+ console . log ( `- Triangles: ${ mesh . geometry . attributes . position . count / 3 } \n` ) ;
115+ } catch ( error ) {
116+ console . error ( "❌ Failed to load STL file:" , error . message ) ;
117+ console . error ( error . stack ) ;
118+ process . exit ( 1 ) ;
119+ }
120+ } else {
121+ console . log ( "Building CSG arch (box minus cylinder) ...\n" ) ;
122+ mesh = createArchMesh ( ) ;
123+ // Simple inspection
124+ const pos = mesh . geometry . attributes . position ;
125+ console . log ( "✅ Arch mesh created via CSG" ) ;
56126 console . log ( `- Geometry type: ${ mesh . geometry . type } ` ) ;
57- console . log ( `- Vertices: ${ mesh . geometry . attributes . position . count } ` ) ;
58- console . log ( `- Triangles: ${ mesh . geometry . attributes . position . count / 3 } \n` ) ;
59- } catch ( error ) {
60- console . error ( '❌ Failed to load STL file:' , error . message ) ;
61- console . error ( error . stack ) ;
62- process . exit ( 1 ) ;
127+ console . log ( `- Vertices: ${ pos ? pos . count : "(unknown)" } ` ) ;
128+ if ( pos ) console . log ( `- Triangles (approx): ${ ( pos . count / 3 ) | 0 } \n` ) ;
63129 }
64130
65131 // Create slicer instance with support enabled.
@@ -68,66 +134,76 @@ async function main() {
68134 filament : filament ,
69135 shellSkinThickness : 0.8 ,
70136 shellWallThickness : 0.8 ,
71- lengthUnit : ' millimeters' ,
72- timeUnit : ' seconds' ,
73- infillPattern : ' hexagons' ,
137+ lengthUnit : " millimeters" ,
138+ timeUnit : " seconds" ,
139+ infillPattern : " hexagons" ,
74140 infillDensity : 30 ,
75141 bedTemperature : 0 ,
76142 layerHeight : 0.2 ,
77143 testStrip : false ,
78144 verbose : true ,
79145 supportEnabled : true ,
80- supportType : ' normal' ,
81- supportPlacement : ' buildPlate' ,
146+ supportType : " normal" ,
147+ supportPlacement : " buildPlate" ,
82148 supportThreshold : 45
83149 } ) ;
84150
85- console . log ( ' Slicer Configuration:' ) ;
151+ console . log ( " Slicer Configuration:" ) ;
86152 console . log ( `- Layer Height: ${ slicer . getLayerHeight ( ) } mm` ) ;
87153 console . log ( `- Nozzle Temperature: ${ slicer . getNozzleTemperature ( ) } °C` ) ;
88154 console . log ( `- Bed Temperature: ${ slicer . getBedTemperature ( ) } °C` ) ;
89155 console . log ( `- Fan Speed: ${ slicer . getFanSpeed ( ) } %` ) ;
90156 console . log ( `- Nozzle Diameter: ${ slicer . getNozzleDiameter ( ) } mm` ) ;
91157 console . log ( `- Filament Diameter: ${ slicer . getFilamentDiameter ( ) } mm` ) ;
92- console . log ( `- Support Enabled: ${ slicer . getSupportEnabled ( ) ? ' Yes' : 'No' } ` ) ;
158+ console . log ( `- Support Enabled: ${ slicer . getSupportEnabled ( ) ? " Yes" : "No" } ` ) ;
93159 console . log ( `- Support Type: ${ slicer . getSupportType ( ) } ` ) ;
94160 console . log ( `- Support Placement: ${ slicer . getSupportPlacement ( ) } ` ) ;
95161 console . log ( `- Support Threshold: ${ slicer . getSupportThreshold ( ) } °` ) ;
96- console . log ( `- Verbose Comments: ${ slicer . getVerbose ( ) ? ' Enabled' : ' Disabled' } \n` ) ;
162+ console . log ( `- Verbose Comments: ${ slicer . getVerbose ( ) ? " Enabled" : " Disabled" } \n` ) ;
97163
98164 // Slice the model with support generation.
99- console . log ( ' Slicing model with support generation...' ) ;
165+ console . log ( " Slicing model with support generation..." ) ;
100166 const startTime = Date . now ( ) ;
101167 const gcode = slicer . slice ( mesh ) ;
102168 const endTime = Date . now ( ) ;
103169
104170 console . log ( `Slicing completed in ${ endTime - startTime } ms\n` ) ;
105171
106172 // Analyze the G-code output.
107- const lines = gcode . split ( '\n' ) ;
108- const layerLines = lines . filter ( line => line . includes ( ' LAYER:' ) ) ;
109- const supportLines = lines . filter ( line => line . toLowerCase ( ) . includes ( ' support' ) ) ;
173+ const lines = gcode . split ( "\n" ) ;
174+ const layerLines = lines . filter ( line => line . includes ( " LAYER:" ) ) ;
175+ const supportLines = lines . filter ( line => line . toLowerCase ( ) . includes ( " support" ) ) ;
110176
111- console . log ( ' G-code Analysis:' ) ;
177+ console . log ( " G-code Analysis:" ) ;
112178 console . log ( `- Total lines: ${ lines . length } ` ) ;
113179 console . log ( `- Layers: ${ layerLines . length } ` ) ;
114180 console . log ( `- Support-related lines: ${ supportLines . length } \n` ) ;
115181
116182 // Save G-code to file.
117- const outputDir = path . join ( __dirname , '..' , ' output' ) ;
183+ const outputDir = path . join ( __dirname , ".." , " output" ) ;
118184
119185 if ( ! fs . existsSync ( outputDir ) ) {
120186 fs . mkdirSync ( outputDir , { recursive : true } ) ;
121187 }
122188
123- const outputPath = path . join ( outputDir , 'block-with-supports.gcode' ) ;
124- fs . writeFileSync ( outputPath , gcode ) ;
189+ const baseName = useStl ? "block-with-supports" : "arch-with-supports" ;
190+ const gcodePath = path . join ( outputDir , `${ baseName } .gcode` ) ;
191+ const stlPath = path . join ( outputDir , `${ baseName } .stl` ) ;
192+ fs . writeFileSync ( gcodePath , gcode ) ;
125193
126- console . log ( `✅ G-code saved to: ${ outputPath } \n` ) ;
194+ // Export STL for the mesh used (mirrors slice-holes behavior)
195+ try {
196+ await exportMeshAsSTL ( mesh , stlPath ) ;
197+ console . log ( `🧊 STL saved to: ${ stlPath } ` ) ;
198+ } catch ( e ) {
199+ console . warn ( `⚠️ Failed to export STL: ${ e . message } ` ) ;
200+ }
201+
202+ console . log ( `✅ G-code saved to: ${ gcodePath } \n` ) ;
127203
128204 // Display support generation info.
129205 if ( supportLines . length > 0 ) {
130- console . log ( ' Support Generation Details:' ) ;
206+ console . log ( " Support Generation Details:" ) ;
131207 supportLines . slice ( 0 , 10 ) . forEach ( line => {
132208 console . log ( ` ${ line . trim ( ) } ` ) ;
133209 } ) ;
@@ -136,11 +212,11 @@ async function main() {
136212 console . log ( ` ... (${ supportLines . length - 10 } more support lines)\n` ) ;
137213 }
138214 } else {
139- console . log ( ' ⚠️ No support structures detected in G-code\n' ) ;
215+ console . log ( " ⚠️ No support structures detected in G-code\n" ) ;
140216 }
141217
142218 // Display some layer information.
143- console . log ( ' Layer Information:' ) ;
219+ console . log ( " Layer Information:" ) ;
144220 const sampleLayers = layerLines . slice ( 0 , 5 ) ;
145221 sampleLayers . forEach ( line => {
146222 console . log ( `- ${ line . trim ( ) } ` ) ;
@@ -150,16 +226,21 @@ async function main() {
150226 console . log ( `... (${ layerLines . length - 5 } more layers)\n` ) ;
151227 }
152228
153- console . log ( '✅ Support generation example completed successfully!' ) ;
154- console . log ( '\nNotes:' ) ;
155- console . log ( '- If no supports were generated, the model may not have overhangs' ) ;
156- console . log ( '- The block.test.stl is a simple rectangular block without overhangs' ) ;
157- console . log ( '- Try the strip.test.stl file which may have overhanging features' ) ;
158- console . log ( '\nNext steps:' ) ;
159- console . log ( '- Load the G-code in a visualizer to inspect the sliced model' ) ;
160- console . log ( '- Try different support thresholds (30°, 45°, 60°) to see the effect' ) ;
161- console . log ( '- Create or load models with overhangs to test support generation' ) ;
162- console . log ( '- Experiment with supportPlacement: "buildPlate" vs "everywhere"' ) ;
229+ console . log ( "✅ Support generation example completed successfully!" ) ;
230+ console . log ( "\nNotes:" ) ;
231+ console . log ( "- If no supports were generated, the model may not have overhangs" ) ;
232+ if ( useStl ) {
233+ console . log ( "- The block.test.stl is a simple rectangular block without overhangs" ) ;
234+ console . log ( "- Try the strip.test.stl file which may have overhanging features" ) ;
235+ } else {
236+ console . log ( "- The CSG arch subtracts a cylinder to create a semi-circular opening" ) ;
237+ console . log ( "- Tweak ARCH_* params at top to adjust width/height/radius" ) ;
238+ }
239+ console . log ( "\nNext steps:" ) ;
240+ console . log ( "- Load the G-code in a visualizer to inspect the sliced model" ) ;
241+ console . log ( "- Try different support thresholds (30°, 45°, 60°) to see the effect" ) ;
242+ console . log ( "- Create or load models with overhangs to test support generation" ) ;
243+ console . log ( "- Experiment with supportPlacement: \"buildPlate\" vs \"everywhere\"" ) ;
163244}
164245
165246// Run the main function
0 commit comments