Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Printable G-code with correct extrusion calculation and misc improvements, more test models #7

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
2021/04/11: 0.8.5: Rene K. Mueller
- adding -S shell_speed=..
- added Makefile and tests/Makefile and more test models

2021/04/06: 0.8.4: Rene K. Mueller
- SVG output: supporting slicing to SVG, using "-o test.svg", very experimental
- intern: model.scale([x,y,z]) implemented
- cli: support for `-M scale=s` or `-M scale=x,y,z`
- intern: fixed vector.py normalize() and __truediv__() supporting STL with bad normals
- un/retract enabled again

2021/04/04: 0.8.3: Rene K. Mueller
- cli & extending configs:
- cool_fan_speed_{min/max}
- cool_fan_full_layer=2 (starting fan after e.g. layer 2 is reached),
- start_gcode and end_gcode and new config type 'str' as well
- skirt_lines=3 only considered if skirt_layers != 0
- gcode_comments = True|False (adding various useful info in Gcode)
- random_pos = True|False: randomize position of model on the build-plate
- all the config included in Gcode
- intern: carrying over 'typ' ('type' in python ;-) of the extrusion in _raw_add_paths,
to refine the retract/unretract, this allowed
have output gcode more descriptive with ';TYPE:..',
- cli: support `-l` or `--load=...` to load more configs (e.g. per printer, or material)
- cli: support `-Q <term>` partial match
- output filament used at the end of slicing
+ most un/retract disabled for now to confirm extrusion calculation working
- prints simple models like 10/20mm cube and xyzHollowCalibrationCubeV2
- finally was able to to run code without build & install, me newbie with python
- changed various `from ... import ...` to work locally
- created Makefile to support `make build install`

40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
APPNAME = mandoline-py
VERSION = 0.8.6

all::
@echo "make build install deinstall tests clean"
@echo "HINT: if you like to code on the base, run 'python3 mandoline/__init__.py' after running build at least once"

build::
#sudo apt install python3-savitar
python3 setup.py build

install::
sudo python3 setup.py install

deinstall::

tests::
cd tests && $(MAKE)

clean::
rm -rf build
cd tests && $(MAKE) clean

# -- devs only:

edit::
${EDITOR} mandoline/*.py setup.py README.rst CHANGES LICENSE Makefile tests/Makefile

change::
git commit -am "..."

push::
git push -u origin master

pull::
git pull

backup::
cd ..; tar cfvz ~/Backup/${APPNAME}-${VERSION}.tar.gz ${APPNAME}; scp ~/Backup/${APPNAME}-${VERSION}.tar.gz backup:Backup/

53 changes: 43 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Mandoline Py
############

This is a 3D printing STL-to-GCode slicer, written in Python, based
on the Clipper geometry library. It will let you take STL files
and generate a GCode path file that you can send to your RepRap 3D
printer to print the object.
This is a 3D printing 3D Model GCode/SVG slicer, written in Python, based
on the Clipper geometry library. It will let you take 3D model files
like STL, 3MF, OBJ, AMF, OFF, 3MJ and generate a GCode path file that you
can send to your RepRap 3D printer to print the object.


Installation
Expand All @@ -27,20 +27,22 @@ Usage
To just validate a model, checking it for manifold errors, just run
``mandoline`` with the name of the file::

mandoline testcube.stl
mandoline cube.stl

Any error messages will be printed to ``STDERR``, and the return code
will be non-zero if errors were found.

To slice a file into GCode, you need to specify the file to write to
with the -o OUTFILE arguments::

mandoline -o testcube.gcode testcube.stl
mandoline -o cube.gcode cube.stl

If you want to force it to skip validation, then add the -n argument::

mandoline -o testcube.gcode -n testcube.stl
mandoline -o cube.gcode -n cube.stl

Settings
--------
To display all slicing config options, use the --show-configs argument::

mandoline --show-configs
Expand All @@ -51,19 +53,50 @@ To get descriptions about all slicing config options, use the --help-configs arg

You can set slicing options on the command-line with -S NAME=VALUE args::

mandoline -S layer_height=0.3 -S skirt_loops=3
mandoline -S layer_height=0.3 -S skirt_lines=3

You can write changed options to the persistent slicing configs file using
the -w argument::

mandoline -S layer_height=0.3 -S brim_loops=3 -w
mandoline -S layer_height=0.3 -S brim_width=3 -w

Query Settings
--------------
You can query the value of a slicing config option with the -q OPTNAME argument::

mandoline -q layer_height -q brim_loops
mandoline -Q layer_height -Q brim_width

Built-in GUI
------------
You can view the sliced output in a GUI window if you add the -g argument.
In this window, up and down arrow keys will move through the slice layers,
and the 'q' key will quit and close the window. The keys `1` - `4` or
`-` and `=` will zoom the image.

TODO
====
* Fixing non-manifold or general get more linient on models
* 3DBenchy fails to slice without `-n`
* Voron_Design_Cube_v7 slices wrong as seen with `-g`
* Allow case-insensitive settings (infill_type, support_type, adhesion_type, bed_geometry)
* Resolve "shell" vs "wall" vs "perimeter" in source variables, source comments and config
* ~~Support more import formats, e.g. 3MF~~ done 0.8.6: 3MF, OBJ, OFF, AMF and 3MJ formats added
* Enable multi-model loading/placement/rotation
* 0.8.4: -M scale=s or -M scale=x,y,z for single model
* Verify models fit inside build volume
* Interior solid infill perimeter paths
* Pathing type prioritization
* Optimize route paths
* Skip retraction for short motions
* Smooth top surfacing for non-flat surfaces
* G-Code custom startup/shutdown/toolchange scripts
* 0.8.3: start_gcode and end_gcode added
* G-Code flavors
* G-Code volumetric extrusion
* Relative E motions
* Better Bridging

See Also
====
https://xyzdims.com/2021/09/16/mandoline-slicer/

111 changes: 94 additions & 17 deletions mandoline/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
# == __init__.py ==
# written by Revar Desmera (2017/11)
# extended by Rene K. Mueller (2021/04)

NAME = 'Mandoline Py'
APPNAME = 'mandoline'
VERSION = '0.8.6' # HINT: keep version consistent also in ../setup.py

import re
import sys
import os.path
from os.path import dirname, abspath, join
import argparse

# import pyximport; pyximport.install()

from .stl_data import StlData
from mandoline.slicer import Slicer
if(__name__!="__main__"):
sys.path.insert(0,abspath(dirname(__file__))) # -- needed when installed, if running locally (without build/install) all works fine

#from stl_data import StlData
from model3d import ModelData
from slicer import Slicer

def main():
parser = argparse.ArgumentParser(prog='myprogram')
print("== {} {} == https://github.com/(Spiritdude|revarbat)/mandoline-py".format(NAME,VERSION))

parser = argparse.ArgumentParser(prog=APPNAME)
parser.add_argument('-o', '--outfile',
help='Slices STL and write GCode to file.')
help='Slices 3D model to GCode or SVGs file.')
parser.add_argument('-n', '--no-validation', action="store_true",
help='Skip performing model validation.')
parser.add_argument('-g', '--gui-display', action="store_true",
help='Show sliced paths output in GUI.')
parser.add_argument('-v', '--verbose', action="store_true",
help='Show verbose output.')
parser.add_argument('-d', '--debug', action="store_true",
help='Show debug output.')

parser.add_argument('--no-raft', dest="set_option", action="append_const",
const="adhesion_type=None", help='Force adhesion to not be generated.')
Expand All @@ -36,6 +53,13 @@ def main():
parser.add_argument('-f', '--filament', metavar="MATERIAL,...",
help='Configures extruder(s) for given materials, in order. Ex: -f PLA,TPU,PVA')

parser.add_argument('-F', '--format', metavar="FORMAT",
help='Set output format (gcode, svg)')
parser.add_argument('-l', '--load', action="append", metavar="OPTNAME",
help='Load config file, containing <k>=<v> lines')

parser.add_argument('-M', '--model', action="append", metavar="OPTNAME=VALUE",
help='Set model manipulation operation(s) (scale).')
parser.add_argument('-S', '--set-option', action="append", metavar="OPTNAME=VALUE",
help='Set a slicing config option.')
parser.add_argument('-Q', '--query-option', action="append", metavar="OPTNAME",
Expand All @@ -46,36 +70,83 @@ def main():
help='Display help for all slicing options.')
parser.add_argument('--show-configs', action="store_true",
help='Display values of all slicing options.')
parser.add_argument('infile', nargs="?", help='Input STL filename.')
parser.add_argument('infile', nargs="?", help='Input 3D model filename (STL, OBJ, OFF, 3MF, AMF, 3MJ).')
args = parser.parse_args()

stl = StlData()
if args.verbose:
print("args",args)

model = [ ]
if args.infile:
stl.read_file(args.infile)
if not os.path.isfile(args.infile):
print("ERROR: model file \"{}\" not found, abort.".format(args.infile))
sys.exit(-1)
model = ModelData()
model.read_file(args.infile)
model.debug = args.debug
if model.points.minz > 0 or model.points.minz < 0: # -- make sure it sits perfectly on Z=0
print("+ Relevel model {:.3f}".format(-model.points.minz))
model.translate([0,0,-model.points.minz])
if args.model: # -- anything to apply on the model(s)
for m in args.model:
ma = re.match("(\w+)=(.*)",m)
if ma[1] and ma[1] == "scale":
v = ma[2].split(",")
if len(v)!=3:
v = [v[0],v[0],v[0]]
v = list(map(lambda x: float(x),v))
print("+ Scaling",v)
model.scale(v)
else:
print("WARN: ignoring {} model operation".format(m))
if args.verbose:
print("Read {0} ({4} facets, {1:.1f} x {2:.1f} x {3:.1f})".format(
args.infile,
stl.points.maxx - stl.points.minx,
stl.points.maxy - stl.points.miny,
stl.points.maxz - stl.points.minz,
len(stl.facets),
model.points.maxx - model.points.minx,
model.points.maxy - model.points.miny,
model.points.maxz - model.points.minz,
len(model.facets),
))

if not args.no_validation:
manifold = True
manifold = stl.check_manifold(verbose=args.verbose)
manifold = model.check_manifold(verbose=args.verbose)
if manifold and (args.verbose or args.gui_display):
print("{} is manifold.".format(args.infile))
if not manifold:
print("ERROR: model is non-manifold and will likely cause bad/missing layers, abort.")
print("HINT: use `-n` switch to skip checking, but the same bad/missing layers remain,")
print(" and or use `-d` to list incomplete polygons in details.")
sys.exit(-1)

slicer = Slicer([stl])

elif not ( args.query_option or args.help_configs or args.show_configs or args.write_configs ):
parser.print_help()
print('''
examples:
mandoline cube.stl
mandoline -l myprinter.ini cube.stl
mandoline -l myprinter.ini -S layer_height=0.3 cube.stl
mandoline -l myprinter.ini -l petg.ini -S infill_type=Triangles cube.stl -o test.gcode
mandoline -Q skirt

''')
sys.exit(0)

slicer = Slicer([model])

slicer.args = args;
slicer.NAME = NAME
slicer.VERSION = VERSION

slicer.load_configs()

if not(args.load is None): # -- any configs listed? load them all in sequence
for c in args.load:
slicer.load_configs(c)

if args.set_option:
for opt in args.set_option:
key, val = opt.split('=', 1)
slicer.set_config(key,val)

if args.filament:
materials = args.filament.lower().split(",")
for extnum,material in enumerate(materials):
Expand All @@ -88,24 +159,30 @@ def main():
print("Configuring extruder{} for {}".format(extnum, material))
slicer.set_config("nozzle_{}_temp".format(extnum), str(slicer.conf['{}_hotend_temp'.format(material)]))
slicer.set_config("nozzle_{}_max_speed".format(extnum), str(slicer.conf['{}_max_speed'.format(material)]))

if args.write_configs:
slicer.save_configs()

if args.query_option:
for opt in args.query_option:
slicer.display_configs_help(key=opt, vals_only=True)

if args.help_configs:
slicer.display_configs_help()

if args.show_configs:
slicer.display_configs_help(vals_only=True)

if args.infile:
if args.outfile:
outfile = args.outfile
else:
outfile = os.path.splitext(args.infile)[0] + ".gcode"
outfile = os.path.splitext(args.infile)[0] + (".svg" if args.format=='svg' else '.gcode')
slicer.slice_to_file(outfile, showgui=args.gui_display)

sys.exit(0)

if(__name__=="__main__"):
main()

# vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
17 changes: 14 additions & 3 deletions mandoline/facet3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
except ImportError:
from itertools import izip_longest as ziplong

from .vector import Vector
from .point3d import Point3D
from .line_segment3d import LineSegment3D
from vector import Vector
from point3d import Point3D
from line_segment3d import LineSegment3D


class Facet3D(object):
Expand Down Expand Up @@ -116,6 +116,11 @@ def translate(self, offset):
for v in self.vertices:
v[a] += offset[a]

def scale(self, scale):
for a in range(3):
for v in self.vertices:
v[a] *= scale[a]

def get_footprint(self, z=None):
if z is None:
path = [v[0:2] for v in self.vertices]
Expand Down Expand Up @@ -267,6 +272,12 @@ def translate(self, offset):
facet.translate(offset)
self.rehash()

def scale(self, scale):
"""Scales vertices of all facets in the facet cache."""
for facet in self.facet_hash.values():
facet.scale(scale)
self.rehash()

def _add_vertex(self, pt, facet):
"""Remember that a given vertex touches a given facet."""
if pt not in self.vertex_hash:
Expand Down
Loading