Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a83605c
Update README.md
johannesnoordanus Oct 9, 2022
26e5294
Makes it executable from the testing directory
johannesnoordanus Oct 9, 2022
92d78c8
Update for new interface
johannesnoordanus Oct 9, 2022
1489c08
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
954bf61
Update to reflect gcode emit changes
johannesnoordanus Oct 9, 2022
89500f8
Updated to reflect gcode emit changes
johannesnoordanus Oct 9, 2022
c907fb4
Updated to reflect gcode emit change
johannesnoordanus Oct 9, 2022
289e903
Updated to reflect new gcode emit
johannesnoordanus Oct 9, 2022
3dc34eb
Update to reflect gcode emit changes
johannesnoordanus Oct 9, 2022
bbd7ed9
Update to reflect the new interface and gcode changes
johannesnoordanus Oct 9, 2022
ad35acb
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
ce08ecd
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
3113640
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
8dec99c
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
6daa4d8
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
6336fc5
Update to reflect new gcode emit
johannesnoordanus Oct 9, 2022
27241a7
Update to reflect new interface
johannesnoordanus Oct 9, 2022
238bce8
Use of upo
johannesnoordanus Oct 9, 2022
a6656e7
Added several methods.
johannesnoordanus Oct 9, 2022
74a69e6
Implemented rapid_move (G0) and 'save' laser mode (M4)
johannesnoordanus Oct 9, 2022
90adbd2
Override the set_laser_power method, use gcode M7 instead of M8
johannesnoordanus Oct 9, 2022
2500cf2
Fix of initialisation issue
johannesnoordanus Nov 19, 2022
e9be4e0
Fix of __repr__ and add_rotation function
johannesnoordanus Nov 19, 2022
4d64137
Fix of laser_off()
johannesnoordanus May 4, 2023
abf4b6c
Update __init__.py
johannesnoordanus May 14, 2023
58d8d74
Update _compiler.py
johannesnoordanus May 14, 2023
fbfae56
Update _gcode.py
johannesnoordanus May 14, 2023
732768a
Update __init__.py
johannesnoordanus May 14, 2023
5d18015
Add files via upload
johannesnoordanus May 14, 2023
b9cec7e
Update __init__.py
johannesnoordanus May 14, 2023
c31c27e
Update _parser_methods.py
johannesnoordanus May 14, 2023
afd89ed
Update README.md
johannesnoordanus May 14, 2023
61d9367
Update README.md
johannesnoordanus May 16, 2023
fe41d88
Update _compiler.py
johannesnoordanus May 18, 2023
a4ebcd9
Update _parser_methods.py
johannesnoordanus May 18, 2023
b322321
Update _path.py
johannesnoordanus May 18, 2023
b045d6e
Image2gcode conversion fix
johannesnoordanus May 23, 2023
c93b471
viewBox fix
johannesnoordanus Jul 17, 2023
4f9746a
fix of absolute_cubic_bezier_extension
johannesnoordanus Oct 11, 2023
24cadc2
Update LICENSE
johannesnoordanus Oct 11, 2023
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
695 changes: 21 additions & 674 deletions LICENSE

Large diffs are not rendered by default.

66 changes: 40 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

# Svg to Gcode - Flamma project
Don't feel like coding? Use the [Inkscape extension](https://github.com/JTechPhotonics/J-Tech-Photonics-Laser-Tool).

This library's intended purpose is to laser-cut svg images. However, it is structured such that it can be easily
expanded to parse other image formats or compile to different numerical control languages.
This library's intended purpose is to laser-cut svg drawings```<svg:path ..> tags```and engrave images```<svg: image ..> tags```.
However, it is structured such that it can be easily expanded to parse other image formats or compile to different numerical control languages.

A commandline steering program is available: see project svg2gcode.

* [Installation](#Installation)
* [Documentation](#Documentation)
Expand All @@ -29,20 +30,28 @@ The module is divided in three sub-modules:

### Basic Usage
If all you need is to compile an svg image to gcode, for a standard cnc machine, this is all the code you need. Just
remember to select your own cutting and movement speeds.
remember to set machine dependend parameter 'maximum laser power' and select your own cutting speed.
Note that all current parameters and defaults can be viewed in svg_to_code/__init__.py

```python
from svg_to_gcode.svg_parser import parse_file
from svg_to_gcode.compiler import Compiler, interfaces

# Instantiate a compiler, specifying the interface type and the speed at which the tool should move. pass_depth controls
# how far down the tool moves after every pass. Set it to 0 if your machine does not support Z axis movement.
gcode_compiler = Compiler(interfaces.Gcode, movement_speed=1000, cutting_speed=300, pass_depth=5)
# Instantiate a compiler, specifying the interface type, maximum_laser_power and speed at which the tool moves while
# cutting. (Note that rapid moves - moves to and from cuts - move at a machine defined speed and are not set here.)
# pass_depth controls how far down the tool moves - how deep the laser cuts - after every pass. Set it to 0 (default)
# if your machine # does not support Z axis movement.
gcode_compiler = Compiler(interfaces.Gcode, params={"maximum_laser_power":1000","movement_speed":900,"pass_depth":5})

# Parse an svg file into geometric curves, and compile to gcode
curves = parse_file("drawing.svg")
gcode_compiler.append_curves(curves)

curves = parse_file("drawing.svg") # Parse an svg file into geometric curves
# do final compilation and emit gcode 2 ('passes') times
gcode_compiler.compile(passes=2)

gcode_compiler.append_curves(curves)
gcode_compiler.compile_to_file("drawing.gcode", passes=2)
# or, to combine the above 2 steps into one and emit to a file:
# gcode_compiler.compile_to_file("drawing.gcode", parse_file("drawing.svg"), passes=2)
```

### Custom interfaces
Expand All @@ -51,36 +60,41 @@ to a completely new numerical control language without modifying the compiler. Y
perform additional operations (like powering a fan) or to modify the gcode commands used to perform existing operations
(some DIY laser cutters, for example, control the laser diode from the fan output).

The code bellow implements a custom interface which powers on a fan every time the laser is powered on.
The code bellow implements a custom interface which powers on a - special mode - fan every time the laser is powered on.
```python

from svg_to_gcode.svg_parser import parse_file
from svg_to_gcode.compiler import Compiler, interfaces
from svg_to_gcode.formulas import linear_map

class CustomInterface(interfaces.Gcode):
def __init__(self):
super().__init__()
self.fan_speed = 1

# Override the laser_off method such that it also powers off the fan.
def laser_off(self):
return "M107;\n" + "M5;" # Turn off the fan + turn off the laser

# Override the set_laser_power method

# Override the set_laser_power method, use gcode M7 - mist coolant - instead of M8 - flood coolant -
# (note that setting 'fan' must be True for this to work)
def set_laser_power(self, power):
if power < 0 or power > 1:
raise ValueError(f"{power} is out of bounds. Laser power must be given between 0 and 1. "
f"The interface will scale it correctly.")
# set power for next linear move
self._next_laser_power = int(linear_map(self._machine_params['minimum_laser_power'], self._machine_params['maximum_laser_power'], power))

# (fan on when available, use M7 instead of M8), laser_on
new_mode = ("\nM7" if self._machine_params['fan'] else '') + ("\n" + self._laser_mode if self._laser_mode_changed else '')
self._laser_mode_changed = False
# return laser mode (M3 constant laser power or M4 dynamic laser power) when laser mode changed
return f"; Cut at {self._next_speed} {self._unit}/min, {int(power * 100)}% power{new_mode}"

return f"M106 S255\n" + f"M3 S{linear_map(0, 255, power)};" # Turn on the fan + change laser power
gcode_compiler = Compiler(CustomInterface, params={"laser_power":1.0,"movement_speed":300,"maximum_laser_power":255,"dwell_time":400,"fan":True})

# Instantiate a compiler, specifying the custom interface and the speed at which the tool should move.
gcode_compiler = Compiler(CustomInterface, movement_speed=1000, cutting_speed=300, pass_depth=5)
# Parse an svg file into geometric curves, and compile to gcode
curves = parse_file("drawing.svg")
gcode_compiler.append_curves(curves)

curves = parse_file("drawing.svg") # Parse an svg file into geometric curves
# do final compilation and emit gcode
gcode_compiler.compile()

gcode_compiler.append_curves(curves)
gcode_compiler.compile_to_file("drawing.gcode")
# or, to combine the above 2 steps into one and emit to a file:
# gcode_compiler.compile_to_file("drawing.gcode", parse_file("drawing.svg"))
```

### Insert or Modify Geometry
Expand Down
116 changes: 114 additions & 2 deletions svg_to_gcode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,114 @@
TOLERANCES = {"approximation": 10 ** -2, "input": 10 ** -3, "operation": 10**-6}
UNITS = {"mm", "in"}
from typing import Any

TOLERANCES = {"approximation": 10 ** -2, "input": 10 ** -3, "operation": 10**-6}
UNITS = {"mm", "inch"}
LASERMODE = {"constant", "dynamic"}
DISTANCEMODE = {"absolute", "incremental"}
SETTING = {
# Machine parameters
"laser_mode_enable", # boolean sets grlb 1.1 laser mode (set default on most laser cutters)
"minimum_laser_power", # positive integer sets lowest power value for laser
"maximum_laser_power", # positive integer sets highest power value for laser (make sure this is set to the machine max)
"x_axis_maximum_rate", # positive integer maximum X-axis speed (typically mm/sec or mm/min) (NOTE: currently unused)
"y_axis_maximum_rate", # positive integer maximum Y-axis speed (typically mm/sec or mm/min) (NOTE: currently unused)
"x_axis_maximum_travel",# positive integer X-axis length of machine work area (mm)
"y_axis_maximum_travel",# positive integer Y-axis length of machine work area (mm)
"fan", # boolean when true 'set_laser_power' enables the fan and 'laser_off' disables the fan.
# Toolparameters
"pass_depth", # positive integer sets tool cutting depth in machine units (Z-axis: the depth your laser cuts in a pass; note that
# this depends on laser power and material!)
"dwell_time", # positive integer sets the number of ms the tool should wait before moving to another cut (useful for pen plotters)
"movement_speed", # positive integer F(eed Rate) parameter; sets the movement speed of the tool while cutting (typically mm/sec or mm/min)
"laser_power", # float range [0..1] sets the laser intensity (Spindle Speed) of the tool. Note that machine parameters
# 'minimum_laser_power' and 'maximum_laser_power' must be set for this to work
"laser_mode", # LASERMODE set constant or dynamic laser power mode
"maximum_image_laser_power",# positive integer sets laser power (maximum) for image drawings (this is typically 1/3) of 'maximum_laser_power')
"image_movement_speed", # positive integer sets movement speed when drawing an image (typically a lot less than the 'maximum_rate')
# Configuration
"unit", # UNITS sets machine unit (milimeters, inches), this also defines the Feed (movement) parameter (mm/min or in/min)
"distance_mode", # DISTANCEMODE sets machine distance mode ('absolute' from 0,0 or 'incremental' from current position)
"rapid_move", # boolean when false, do not use rapid move (G0) to go to the next line chain, use a 'slow' (G1) move with laser
# power off (S0) (note that the first move - to the start of the first line chain - is still a rapid move)
"pixel_size", # float sets image pixel size (in mm)
"showimage" # boolean show image used for conversion to gcode
}

# Set defaults 'minimum_laser_power', 'pass_depth', 'dwell_time', 'laser_power', 'laser_mode', 'unit', 'distance_mode'
DEFAULT_SETTING = {
# Machine parameters
"laser_mode_enable": None, # usually already on
"minimum_laser_power": 0, # default set to 0
"maximum_laser_power": None, # MANDATORY ('enter $$' on the machine console to get this - and other machine parameters)
"x_axis_maximum_rate": None, # currently unused
"y_axis_maximum_rate": None, # currently unused
"x_axis_maximum_travel":None, # set this to the length of the machine x-axis (in mm)
"y_axis_maximum_travel":None, # set this to the length of the machine y-axis (in mm)
"fan": False, # default set to false (no fan, not on)
# Toolparameters
"pass_depth": 0, # default set to 0 (no depth)
"dwell_time": 0, # default set to 0 (do not linger)
"movement_speed": None, # MANDATORY
"laser_power": 1, # default set to 1
"laser_mode": "dynamic", # default set to dynamic (M4 safest mode, laser is off when not moving)
"maximum_image_laser_power":None, # MANDATORY should be ("maximum_laser_power" - "minimum_laser_power" / 3)
"image_movement_speed": None, # MANDATORY
# Configuration
"unit": "mm", # default set to milimeters
"distance_mode": "absolute", # default set to absolute
"rapid_move": True, # default set to true
"pixel_size": 0.1, # laser kerf is mostly < 0.1mm
"showimage": False # default image is not shown
}

def check_setting(setting: dict[str,Any] =None) -> bool:

"""
Check all settings on type and value range.
:paran setting: dictionary containg all settings.
:return returns True when all settings are of the right type and within range.

Note that because these settings determain real world parameters of machines that have possibly lots of (laser) power, type and range
mistakes can have serious consequences. Also in general, to contain programming errors, it is best to - in this case - use 'mypy' and
'pylint' to check typing and formatting problems.

"""

if setting is None:
return False

for key in setting.keys():
if key not in SETTING:
raise ValueError(f"Unknown setting {key}. Please specify one of the following: {SETTING}")
# Machine parameters
if key == "laser_mode_enable" and setting[key] not in {True,False}:
raise ValueError(f"Unknown '{key}' value '{setting[key]}'. Please specify one of the following: {{True,False}}")
if key in {"minimum_laser_power","maximum_laser_power","x_axis_maximum_rate","y_axis_maximum_rate", "movement_speed",
"x_axis_maximum_travel","y_axis_maximum_travel", "maximum_image_laser_power",
"image_movement_speed" } and (not isinstance(setting[key],int) or setting[key] < 0):
raise ValueError(f"'{key}' has type {type(setting[key])} and value {setting[key]}, but should be of type {type(1)} and have a value >= 0")
# Toolparameters
if key in {"pass_depth","dwell_time"}:
if not isinstance(setting[key],(int,float)) or setting[key] < 0:
raise ValueError(f"'{key}' has type {type(setting[key])} and value {setting[key]}, but should be of type {type(1)} and have a value >= 0")
setting[key] = float(setting[key])
if key == "laser_power":
if not isinstance(setting[key],(int,float)):
raise TypeError(f"'{key}' is of type '{type(setting[key])}' but should be of type {type(1.0)}")
if setting[key] > 1 or setting[key] < 0:
raise ValueError(f"'{key}' value {setting[key]} is not in range [0..1]")
setting[key] = float(setting[key])
if key == "laser_mode" and setting[key] not in LASERMODE:
raise ValueError(f"Unknown '{key}' '{setting[key]}'. Please specify one of the following: {LASERMODE}")
# Configuration
if key == "unit" and setting[key] not in UNITS:
raise ValueError(f"Unknown '{key}' value '{setting[key]}'. Please specify one of the following: {UNITS}")
if key == "distance_mode" and setting[key] not in DISTANCEMODE:
raise ValueError(f"Unknown '{key}' value '{setting[key]}'. Please specify one of the following: {DISTANCEMODE}")
if key == "rapid_move" and setting[key] not in {True,False}:
raise ValueError(f"Unknown '{key}' value '{setting[key]}'. Please specify one of the following: {{True,False}}")
if key in {"fan","showimage"} and setting[key] not in {True,False}:
raise ValueError(f"Unknown '{key}' value '{setting[key]}'. Please specify one of the following: {{True,False}}")
if key == "pixel_size" and setting[key] and (not isinstance(setting[key],(float)) or setting[key] <= 0):
raise TypeError(f"'{key}' is of type '{type(setting[key])}' but should be of type {type(1.0)} and have a value > 0.0")

return True
Loading