Skip to content

Commit

Permalink
Merge pull request #41 from aligator/lib
Browse files Browse the repository at this point in the history
Adapt GoSlice struct to be fully exported. This allows easy usage as lib.
  • Loading branch information
aligator authored Mar 29, 2021
2 parents 1490981 + ae412c1 commit ba2282a
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 55 deletions.
64 changes: 61 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# GoSlice

This is a very experimental slicer for 3d printing. It is currently in a very early stage but it can already slice models:
This is a very experimental slicer for 3d printing. It is currently in a very early stage, but it can already slice models:

Supported features:
__Supported features:__
* perimeters
* simple linear infill
* rotated infill
Expand All @@ -18,11 +18,18 @@ Supported features:
* simple support generation
* brim and skirt

__For users - Use CLI:__
Provides a basic command line interface. Just run with `--help` and see the description bellow.

__For developers - Use as Go library:__
You can use GoSlice as slicing lib, with support to inject custom slicing logic at any stage.
See __"Use as lib"__ bellow.

Example:
<img width="200" alt="sliced Gopher logo" src="https://raw.githubusercontent.com/aligator/GoSlice/master/docs/GoSlice-print.png">

## Try it out - for users
Download latest release matching your platform from here:
Download the latest release matching your platform from here:
https://github.com/aligator/GoSlice/releases

Unpack the executable and run it in the commandline.
Expand All @@ -41,6 +48,9 @@ If you need the usage of all possible flags, run it with the `--help` flag:
./goslice --help
```

Note that some flags exist as --initial-... also which applies to the first layer only.
The non-initial apply to all other layers, but not the first one.

## Try it out - for developers
Just running GoSlice:
```
Expand All @@ -65,6 +75,54 @@ go build -ldflags "-X=main.Version=$(git describe --tags) -X=main.Build=$(git re
## How does it work
[see here](docs/README.md)

## Use as lib
You want to
* Create a slicer but do not want to do everything of it?
* Extend GoSlice functionality? (Please consider Pull Requests if you created a nice addition :-)
* Create a new, user-friendly frontend?

-> Then you can do it with GoSlice!

To do this you can copy the `goslice/slicer.go/NewGoSlice` function and just pass to GoSlice what you want.
You can add new logic by implementing one of the various handler interfaces used by it.
If you need even more control, you can even copy and modify the whole `goslice/slicer.go` file which allows you to
control how the steps are called after each other.

### Handler Interfaces
Here some brief explanation of the interfaces. For more detailed information just look into the code...
(And take a look at [the docs](docs/README.md) where I explained some aspects a bit deeper.)
* Reader handler.ModelReader
Is used to read a mesh file. GoSlice provides an implementation for stl files.

* Optimizer handler.ModelOptimizer
Is responsible for
1. checking the model
2. optimizing it by e.g. removing doubles
3. calculating some additional information,
like the touching vertices etc. which is needed for the next step.
The implementation of GoSlice is very currently basic and may have problems with some models.

* Slicer handler.ModelSlicer
Creates the slices (e.g. layers) out of the model.
It then tries to combine all lines to several polygons per each layer.
The implementation of GoSlice is again very basic, but it works.

* Modifiers []handler.LayerModifier
This is the most interesting part: Modifiers are called after each other and
Calculate things like perimeters, infill, support, ...
They add this information as "Attributes" which is basically just a map of interface{}.
GoSlice already provides several basic modifiers.

* Generator handler.GCodeGenerator
The generator then generates the final gcode based on the data the modifiers added.
The implementation of GoSlice is basically a collection of `Renderer` which often just match one modifier.
You can provide your own, additional Renderers or even replace existing ones.

* Writer handler.GCodeWriter
This is the last part, and it basically just writes the gcode to somewhere.
You could for example provide a writer which directly sends the gcode to OctoPrint.
The default implementation just writes it to a gcode file.

## Contribution
You are welcome to help.
[Just look for open issues](https://github.com/aligator/GoSlice/issues) and pick one, create new issues or create new pull requests.
Expand Down
3 changes: 2 additions & 1 deletion cmd/goslice/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"GoSlice"
"GoSlice/data"
"fmt"
"io"
Expand All @@ -25,7 +26,7 @@ func main() {
os.Exit(1)
}

p := NewGoSlice(o)
p := GoSlice.NewGoSlice(o)
err := p.Process()

if err != nil {
Expand Down
38 changes: 23 additions & 15 deletions data/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@ type GoSliceOptions struct {
// PrintVersion indicates if the GoSlice version should be printed.
PrintVersion bool

// InputFilePath specifies the path to the input stl file.
InputFilePath string

// OutputFilePath specifies the path to the output gcode file.
OutputFilePath string
}

// SlicingOptions contains all options related to slice a model.
type SlicingOptions struct {
// MeldDistance is the distance which two points have to be
// within to count them as one point.
MeldDistance Micrometer
Expand All @@ -272,16 +281,11 @@ type GoSliceOptions struct {
// FinishPolygonSnapDistance is the max distance between start end endpoint of
// a polygon used to check if a open polygon can be closed.
FinishPolygonSnapDistance Micrometer

// InputFilePath specifies the path to the input stl file.
InputFilePath string

// OutputFilePath specifies the path to the output gcode file.
OutputFilePath string
}

// Options contains all GoSlice options.
type Options struct {
Slicing SlicingOptions
Printer PrinterOptions
Filament FilamentOptions
Print PrintOptions
Expand All @@ -290,6 +294,11 @@ type Options struct {

func DefaultOptions() Options {
return Options{
Slicing: SlicingOptions{
MeldDistance: 30,
JoinPolygonSnapDistance: 160,
FinishPolygonSnapDistance: 1000,
},
Print: PrintOptions{
IntialLayerSpeed: 30,
LayerSpeed: 60,
Expand Down Expand Up @@ -340,12 +349,9 @@ func DefaultOptions() Options {
),
},
GoSlice: GoSliceOptions{
PrintVersion: false,
MeldDistance: 30,
JoinPolygonSnapDistance: 160,
FinishPolygonSnapDistance: 1000,
InputFilePath: "",
OutputFilePath: "",
PrintVersion: false,
InputFilePath: "",
OutputFilePath: "",
},
}
}
Expand All @@ -362,11 +368,13 @@ func ParseFlags() Options {

// GoSlice options
flag.BoolVarP(&options.GoSlice.PrintVersion, "version", "v", false, "Print the GoSlice version.")
flag.Var(&options.GoSlice.MeldDistance, "meld-distance", "The distance which two points have to be within to count them as one point.")
flag.Var(&options.GoSlice.JoinPolygonSnapDistance, "join-polygon-snap-distance", "The distance used to check if two open polygons can be snapped together to one bigger polygon. Checked by the start and endpoints of the polygons.")
flag.Var(&options.GoSlice.FinishPolygonSnapDistance, "finish-polygon-snap-distance", "The max distance between start end endpoint of a polygon used to check if a open polygon can be closed.")
flag.StringVarP(&options.GoSlice.OutputFilePath, "output", "o", options.GoSlice.OutputFilePath, "File path for the output gcode file. Default is the inout file path with .gcode as file ending.")

// Slicing options
flag.Var(&options.Slicing.MeldDistance, "meld-distance", "The distance which two points have to be within to count them as one point.")
flag.Var(&options.Slicing.JoinPolygonSnapDistance, "join-polygon-snap-distance", "The distance used to check if two open polygons can be snapped together to one bigger polygon. Checked by the start and endpoints of the polygons.")
flag.Var(&options.Slicing.FinishPolygonSnapDistance, "finish-polygon-snap-distance", "The max distance between start end endpoint of a polygon used to check if a open polygon can be closed.")

// print options
flag.Var(&options.Print.IntialLayerSpeed, "initial-layer-speed", "The speed only for the first layer in mm per second.")
flag.Var(&options.Print.LayerSpeed, "layer-speed", "The speed for all but the first layer in mm per second.")
Expand Down
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb h1:61ndUreYSlWFeCY44JxD
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
github.com/paulmach/orb v0.1.6/go.mod h1:pPwxxs3zoAyosNSbNKn1jiXV2+oovRDObDKfTvRegDI=
github.com/paulmach/osm v0.1.1/go.mod h1:/UEV7XqKKTG3/46W+MtSmIl81yjV7cGoLkpol3S094I=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -46,7 +45,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand All @@ -59,7 +57,6 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
48 changes: 24 additions & 24 deletions cmd/goslice/slicer.go → goslice.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package GoSlice

import (
"GoSlice/clip"
Expand All @@ -18,30 +18,30 @@ import (
// GoSlice combines all logic needed to slice
// a model and generate a GCode file.
type GoSlice struct {
options *data.Options
reader handler.ModelReader
optimizer handler.ModelOptimizer
slicer handler.ModelSlicer
modifiers []handler.LayerModifier
generator handler.GCodeGenerator
writer handler.GCodeWriter
Options data.GoSliceOptions
Reader handler.ModelReader
Optimizer handler.ModelOptimizer
Slicer handler.ModelSlicer
Modifiers []handler.LayerModifier
Generator handler.GCodeGenerator
Writer handler.GCodeWriter
}

// NewGoSlice provides a GoSlice with all built in implementations.
func NewGoSlice(options data.Options) *GoSlice {
s := &GoSlice{
options: &options,
Options: options.GoSlice,
}

// create handlers
topBottomPatternFactory := func(min data.MicroPoint, max data.MicroPoint) clip.Pattern {
return clip.NewLinearPattern(options.Printer.ExtrusionWidth, options.Printer.ExtrusionWidth, min, max, options.Print.InfillRotationDegree, true, false)
}

s.reader = reader.Reader(&options)
s.optimizer = optimizer.NewOptimizer(&options)
s.slicer = slicer.NewSlicer(&options)
s.modifiers = []handler.LayerModifier{
s.Reader = reader.Reader(&options)
s.Optimizer = optimizer.NewOptimizer(&options)
s.Slicer = slicer.NewSlicer(&options)
s.Modifiers = []handler.LayerModifier{
modifier.NewPerimeterModifier(&options),
modifier.NewInfillModifier(&options),
modifier.NewInternalInfillModifier(&options),
Expand All @@ -52,7 +52,7 @@ func NewGoSlice(options data.Options) *GoSlice {

patternSpacing := options.Print.Support.PatternSpacing.ToMicrometer()

s.generator = gcode.NewGenerator(
s.Generator = gcode.NewGenerator(
&options,
gcode.WithRenderer(renderer.PreLayer{}),
gcode.WithRenderer(renderer.Skirt{}),
Expand Down Expand Up @@ -117,7 +117,7 @@ func NewGoSlice(options data.Options) *GoSlice {
}),
gcode.WithRenderer(renderer.PostLayer{}),
)
s.writer = writer.Writer()
s.Writer = writer.Writer()

return s
}
Expand All @@ -126,15 +126,15 @@ func (s *GoSlice) Process() error {
startTime := time.Now()

// 1. Load model
models, err := s.reader.Read(s.options.GoSlice.InputFilePath)
models, err := s.Reader.Read(s.Options.InputFilePath)
if err != nil {
return err
}

// 2. Optimize model
var optimizedModel data.OptimizedModel

optimizedModel, err = s.optimizer.Optimize(models)
optimizedModel, err = s.Optimizer.Optimize(models)
if err != nil {
return err
}
Expand All @@ -145,15 +145,15 @@ func (s *GoSlice) Process() error {
//}

// 3. Slice model into layers
layers, err := s.slicer.Slice(optimizedModel)
layers, err := s.Slicer.Slice(optimizedModel)
if err != nil {
return err
}

// 4. Modify the layers
// e.g. generate perimeter paths,
// generate the parts which should be filled in, ...
for _, m := range s.modifiers {
for _, m := range s.Modifiers {
m.Init(optimizedModel)
err = m.Modify(layers)
if err != nil {
Expand All @@ -162,18 +162,18 @@ func (s *GoSlice) Process() error {
}

// 5. generate gcode from the layers
s.generator.Init(optimizedModel)
finalGcode, err := s.generator.Generate(layers)
s.Generator.Init(optimizedModel)
finalGcode, err := s.Generator.Generate(layers)
if err != nil {
return err
}

outputPath := s.options.GoSlice.OutputFilePath
outputPath := s.Options.OutputFilePath
if outputPath == "" {
outputPath = s.options.GoSlice.InputFilePath + ".gcode"
outputPath = s.Options.InputFilePath + ".gcode"
}

err = s.writer.Write(finalGcode, outputPath)
err = s.Writer.Write(finalGcode, outputPath)
fmt.Println("full processing time:", time.Now().Sub(startTime))

return err
Expand Down
6 changes: 3 additions & 3 deletions cmd/goslice/slicer_test.go → goslice_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package GoSlice

import (
"GoSlice/data"
Expand All @@ -7,7 +7,7 @@ import (
)

const (
folder = "../../test_stl/"
folder = "./test_stl/"

// The models are copied to the project just to avoid downloading them for each test.

Expand Down Expand Up @@ -44,7 +44,7 @@ func TestWholeSlicer(t *testing.T) {

for _, testCase := range tests {
t.Log("slice " + testCase.path)
s.options.GoSlice.InputFilePath = folder + testCase.path
s.Options.InputFilePath = folder + testCase.path
err := s.Process()
test.Ok(t, err)
}
Expand Down
4 changes: 2 additions & 2 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type GCodeGenerator interface {
Generate(layer []data.PartitionedLayer) (string, error)
}

// GCodeWriter writes the given GCode into the given file.
// GCodeWriter writes the given GCode into the given destination.
type GCodeWriter interface {
Write(gcode string, filename string) error
Write(gcode string, destination string) error
}
4 changes: 2 additions & 2 deletions optimizer/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ FacesLoop:
currentPoint := face.Points()[j]
// create hash for the pos
// points which are within the meldDistance fall into the same category of the indices map
meldDistanceHash := pointHash(o.options.GoSlice.MeldDistance)
meldDistanceHash := pointHash(o.options.Slicing.MeldDistance)
hash := ((pointHash(currentPoint.X()) + meldDistanceHash/2) / meldDistanceHash) ^
(((pointHash(currentPoint.Y()) + meldDistanceHash/2) / meldDistanceHash) << 10) ^
(((pointHash(currentPoint.Z()) + meldDistanceHash/2) / meldDistanceHash) << 20)
Expand All @@ -72,7 +72,7 @@ FacesLoop:
// is smaller (or same) than the currently tested pos
for _, index := range indices[hash] {
differenceVec := om.points[index].pos.Sub(currentPoint)
if differenceVec.ShorterThanOrEqual(o.options.GoSlice.MeldDistance) {
if differenceVec.ShorterThanOrEqual(o.options.Slicing.MeldDistance) {
// if true for any of the points with the same hash,
// do not add the current pos to the indices map
// but save the indices of the already existing duplicate
Expand Down
Loading

0 comments on commit ba2282a

Please sign in to comment.