Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the page size (#517)
- Import: GIF, TIFF and WebP bitmaps, traced like PNG. Animated GIFs
and multi-page TIFFs import their first frame or page (#517)
- Engrave: more dithering algorithms for photos. The error-diffusion
kernels Atkinson, Stucki, Jarvis-Judice-Ninke, Sierra (3-row,
2-row, Lite) and Burkes, with an optional serpentine scan, plus a
clustered-dot Newsprint screen and a round-dot Halftone screen with
adjustable cell size and angle. LightBurn imports keep the image
layer's dither mode (#514)
- Sketcher: boolean operations. Union, Difference, Intersection and
Exclude bake the selected closed regions into multi-ring polygons
(outer contours counter-clockwise, hole contours clockwise), so
Expand Down Expand Up @@ -55,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Step settings: choice rows backed by an enum (such as the Engrave
step's dithering algorithm) now show the step's actual value when
the dialog opens and after undo, instead of the first entry
- Ruida: the setup wizard no longer requires the optional USB device
and Magic fields, so Next is enabled again once a valid hostname is
entered; the Magic field is prefilled with the standard 0x88
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ We also have a [Discord](https://discord.gg/sTHNdTtpQJ).
| **Animated 3D Simulation** | Simulate toolpaths in 3D with animated playback, scrubber, and speed control. |
| **Holding Tabs** | Add tabs to contour cuts. Supports manual and automatic placement. |
| **Overscan & Kerf Comp.** | Improve engraving quality with overscan; ensure dimensional accuracy with kerf compensation. |
| **Dithering Algorithms** | Floyd-Steinberg and Bayer ordered dithering for high-quality raster engraving. |
| **Dithering Algorithms** | Floyd-Steinberg, Stucki, Jarvis, Atkinson, Sierra, Burkes, Bayer, newsprint and halftone screens. |
| **Post-Processors** | Lead-in/lead-out, merge overlapping lines, and crop toolpaths to stock boundary. |
| **Advanced Path Generation** | Image tracing, travel time optimization, path smoothing, and spot size interpolation. |

Expand Down
2 changes: 1 addition & 1 deletion debian/requirements-bundle.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
asyncudp==0.11.0
pyvips==3.2.0
raydriver==0.3.0
raygeo==1.61.0
raygeo==1.62.0
ruida-pa==0.21.2
trimesh==5.1.1
vtracer==0.6.15
109 changes: 59 additions & 50 deletions pixi.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ rust = "==1.98.1"
patchelf = "==0.19.2"

[feature.app.pypi-dependencies]
raygeo = "==1.61.0"
raygeo = "==1.62.0"
raydriver = "==0.3.0"
rayforge = { editable = true, path = "." }
ruida-pa = "==0.22.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
SliderIntVar,
VarSet,
)
from rayforge.image.dither import DitherAlgorithm
from rayforge.image.dither import (
DEFAULT_HALFTONE_ANGLE,
DEFAULT_HALFTONE_CELL_MM,
DitherAlgorithm,
)
from rayforge.machine.models.laser import LaserHead
from rayforge.pipeline.stage.assembler_helpers import (
DepthMode,
Expand Down Expand Up @@ -76,6 +80,22 @@ def is_dither(v):
def is_multi_pass(v):
return v.get("depth_mode") == "MULTI_PASS"

def dither_algorithm(v):
name = v.get("dither_algorithm") or "FLOYD_STEINBERG"
return DitherAlgorithm.__members__.get(name)

def is_error_diffusion(v):
algo = dither_algorithm(v)
return (
is_dither(v) and algo is not None and algo.is_error_diffusion
)

def is_halftone(v):
return (
is_dither(v)
and dither_algorithm(v) is DitherAlgorithm.HALFTONE
)

def uses_grayscale(v):
return is_power(v) or is_multi_pass(v)

Expand Down Expand Up @@ -114,6 +134,35 @@ def uses_levels(v):
visible_when=is_dither,
allow_none=False,
),
BoolVar(
key="dither_serpentine",
label=_("Serpentine"),
description=_(
"Alternate the dither direction on every row to "
"avoid directional artifacts"
),
default=False,
visible_when=is_error_diffusion,
),
LengthVar(
key="halftone_cell_mm",
label=_("Halftone Cell Size"),
description=_("Distance between halftone dot centers"),
default=DEFAULT_HALFTONE_CELL_MM,
min_val=0.01,
max_val=50.0,
digits=3,
visible_when=is_halftone,
),
AngleVar(
key="halftone_angle",
label=_("Halftone Angle"),
description=_("Rotation of the halftone dot grid"),
default=DEFAULT_HALFTONE_ANGLE,
min_val=0.0,
max_val=180.0,
visible_when=is_halftone,
),
ScanAngleVar(),
BoolVar(
key="cross_hatch",
Expand Down Expand Up @@ -305,6 +354,9 @@ def __init__(self, name: str | None = None, typelabel: str | None = None):
self.z_step_down = 0.0
self.angle_increment = 0.0
self.dither_algorithm = None
self.dither_serpentine = False
self.halftone_cell_mm = DEFAULT_HALFTONE_CELL_MM
self.halftone_angle = DEFAULT_HALFTONE_ANGLE
self.bidir_x_offset_mm = 0.0

def set_dither_algorithm(self, algorithm: DitherAlgorithm | str | None):
Expand Down Expand Up @@ -394,9 +446,16 @@ def apply_import_settings(self, settings: dict[str, Any]) -> None:
"dot_width_correction_mm",
"line_interval_mm",
"scan_angle",
"depth_mode",
"threshold",
"dither_serpentine",
"halftone_cell_mm",
"halftone_angle",
):
if key in settings:
setattr(self, key, settings[key])
if "dither_algorithm" in settings:
self.set_dither_algorithm(settings["dither_algorithm"])

def build_compute_payload(
self,
Expand Down Expand Up @@ -481,6 +540,9 @@ def get_cache_params(self) -> dict:
if self.dither_algorithm is not None
else None
),
"dither_serpentine": self.dither_serpentine,
"halftone_cell_mm": self.halftone_cell_mm,
"halftone_angle": self.halftone_angle,
}
)
return params
Expand Down Expand Up @@ -510,6 +572,9 @@ def to_dict(self) -> dict:
result["dither_algorithm"] = (
self.dither_algorithm.value if self.dither_algorithm else None
)
result["dither_serpentine"] = self.dither_serpentine
result["halftone_cell_mm"] = self.halftone_cell_mm
result["halftone_angle"] = self.halftone_angle
result["bidir_x_offset_mm"] = self.bidir_x_offset_mm
return result

Expand Down Expand Up @@ -600,6 +665,13 @@ def from_dict(cls, data: dict) -> EngraveStep:
step.dither_algorithm = DitherAlgorithm(dither_val)
except ValueError:
step.dither_algorithm = DitherAlgorithm.FLOYD_STEINBERG
step.dither_serpentine = data.get("dither_serpentine", False)
step.halftone_cell_mm = data.get(
"halftone_cell_mm", DEFAULT_HALFTONE_CELL_MM
)
step.halftone_angle = data.get(
"halftone_angle", DEFAULT_HALFTONE_ANGLE
)
step.bidir_x_offset_mm = data.get("bidir_x_offset_mm", 0.0)
return step

Expand Down Expand Up @@ -628,6 +700,9 @@ def _serialized_keys(cls) -> frozenset[str]:
"z_step_down",
"angle_increment",
"dither_algorithm",
"dither_serpentine",
"halftone_cell_mm",
"halftone_angle",
"bidir_x_offset_mm",
"min_power",
"max_power",
Expand Down Expand Up @@ -771,6 +846,10 @@ def _build_raster_part(
dither_algorithm=step.dither_algorithm,
laser_spot_x_mm=spot_x,
pixels_per_mm_x=px_per_mm_x,
pixels_per_mm_y=px_per_mm_y,
dither_serpentine=step.dither_serpentine,
halftone_cell_mm=step.halftone_cell_mm,
halftone_angle=step.halftone_angle,
)
surface.flush()
if image is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"depth_mode",
"threshold",
"dither_algorithm",
"dither_serpentine",
"halftone_cell_mm",
"halftone_angle",
"scan_angle",
"cross_hatch",
"scan_mode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,3 +802,27 @@ msgstr "Laserkopf"
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr "Materialtest hinzufügen"

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr "Serpentinenmodus"

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr "Ditherrichtung in jeder Zeile wechseln, um richtungsabhängige Artefakte zu vermeiden"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr "Halbton-Zellgröße"

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr "Abstand zwischen den Mittelpunkten der Halbtonpunkte"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr "Halbton-Winkel"

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr "Drehung des Halbton-Punktrasters"
Original file line number Diff line number Diff line change
Expand Up @@ -770,3 +770,27 @@ msgstr ""
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr "Add Material Test"

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr ""
Original file line number Diff line number Diff line change
Expand Up @@ -806,3 +806,27 @@ msgstr "Cabezal láser"
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr "Añadir prueba de material"

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr "Serpentina"

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr "Alternar la dirección del difuminado en cada fila para evitar artefactos direccionales"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr "Tamaño de celda del semitono"

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr "Distancia entre los centros de los puntos del semitono"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr "Ángulo del semitono"

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr "Rotación de la cuadrícula de puntos del semitono"
Original file line number Diff line number Diff line change
Expand Up @@ -803,3 +803,27 @@ msgstr "Tête laser"
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr "Ajouter un test de matériau"

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr "Serpentin"

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr "Alterner le sens du tramage à chaque ligne pour éviter les artefacts directionnels"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr "Taille de cellule de demi-teinte"

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr "Distance entre les centres des points de demi-teinte"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr "Angle de demi-teinte"

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr "Rotation de la grille de points de demi-teinte"
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,27 @@ msgstr "लेज़र हेड"
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr "सामग्री परीक्षण जोड़ें"

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr "सर्पाकार"

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr "दिशात्मक दोषों से बचने के लिए हर पंक्ति में डाइदर की दिशा बदलें"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr "हाफ़टोन सेल आकार"

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr "हाफ़टोन बिंदुओं के केंद्रों के बीच की दूरी"

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr "हाफ़टोन कोण"

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr "हाफ़टोन बिंदु ग्रिड का घुमाव"
Original file line number Diff line number Diff line change
Expand Up @@ -774,3 +774,27 @@ msgstr ""
#: laser_essentials/commands/material_test_cmd.py
msgid "Add Material Test"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Serpentine"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Alternate the dither direction on every row to avoid directional artifacts"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Halftone Cell Size"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Distance between halftone dot centers"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Halftone Angle"
msgstr ""

#: laser_essentials/steps/raster_step.py
msgid "Rotation of the halftone dot grid"
msgstr ""
Loading
Loading