Skip to content

Instantly share code, notes, and snippets.

@the-ress
Last active February 12, 2022 19:44
Show Gist options
  • Save the-ress/cb3f5858d8aed80157bc3c3284794bb3 to your computer and use it in GitHub Desktop.
Save the-ress/cb3f5858d8aed80157bc3c3284794bb3 to your computer and use it in GitHub Desktop.
PCB printing
$ErrorActionPreference = 'Stop'
$filename = 'ventilation-controller-B_Cu-flatcam_0025.gcode'
[decimal]$zadj = 0.5
[decimal]$targetx = 36
[decimal]$targety = 48
$g1regex = '^g1 x([\d.]+) y([\d.]+)$'
$zregex = '^g0 z([\d.]+)(.*)$'
$newfilename = '{0}_adjusted.gcode' -f [System.IO.Path]::GetFileNameWithoutExtension($filename)
$testPatternFilename = '{0}_test_pattern.gcode' -f [System.IO.Path]::GetFileNameWithoutExtension($filename)
$gcode = Get-Content $filename
. ./analyze-gcode.ps1
$props = Analyze-Gcode $filename
$xadj = $targetx - $props.xmin
$yadj = $targety - $props.ymin
$gcode |% {
if ($_ -match $g1regex) {
$x = [decimal]$Matches[1] + $xadj;
$y = [decimal]$Matches[2] + $yadj;
return "G1 X$x Y$y"
}
if ($_ -match $zregex) {
$z = [decimal]$Matches[1] + $zadj;
$rest = $Matches[2];
return "G0 Z$z$rest"
}
return $_
} | Set-Content $newfilename
Analyze-Gcode $newfilename | Out-Null
[decimal]$loweredz = 3 + $zadj
[decimal]$raisedz = $loweredz + 1.5
$lowerPen = "G0 Z$loweredz F1200"
$raisePen = "G0 Z$raisedz F1200"
$wait = 'G4 P0'
$testPattern = @"
G90
G0 Z50 F1200
G28 X0 Y0
G1 X80 Y120 F6000
$wait
$lowerPen
$wait
G1 X100 Y120 F2000
$wait
$raisePen
$wait
G1 X80 Y122 F6000
$wait
$lowerPen
$wait
G1 X100 Y122 F2000
$wait
$raisePen
$wait
G1 X80 Y124 F6000
$wait
$lowerPen
$wait
G1 X100 Y124 F2000
$wait
$raisePen
$wait
"@
$xpattern = @"
; {0} {1} {2} {3}
G1 F6000
G1 X{0} Y{1}
$wait
$lowerPen
$wait
G1 F2000
G1 X{2} Y{3}
$wait
$raisePen
$wait
G1 F6000
G1 X{0} Y{3}
$wait
$lowerPen
$wait
G1 F2000
G1 X{2} Y{1}
$wait
$raisePen
$wait
"@
$xmin = $targetx
$ymin = $targety
$xmax = $targetx + $props.w
$ymax = $targety + $props.h
$testPattern += $xpattern -f $xmin, $ymin, ($xmin + 10), ($ymin + 10)
$testPattern += $xpattern -f $xmin, $ymax, ($xmin + 10), ($ymax - 10)
$testPattern += $xpattern -f $xmax, $ymin, ($xmax - 10), ($ymin + 10)
$testPattern += $xpattern -f $xmax, $ymax, ($xmax - 10), ($ymax - 10)
$testPattern += $xpattern -f ($xmin + 10), ($ymin + 10), ($xmax - 10), ($ymax - 10)
$testPattern += @"
G0 X0 Y210 Z100
M18
"@
$testPattern | Set-Content $testPatternFilename
Analyze-Gcode $testPatternFilename | Out-Null
# 1.5949
# -22.5299 -55.1099
# 77.4701 -55.1099
# -20.935 -53.515
# 79.065 -53.515
# -24.305; -57.335
# 75.695; -57.335
# mm -> px@96 3.779527559055118
$ErrorActionPreference = 'Stop'
function Analyze-Gcode {
param (
$filename
)
$pngfilename = '{0}.png' -f [System.IO.Path]::GetFileNameWithoutExtension($filename)
$gcode = Get-Content $filename
$g1 = $gcode |? { $_ -match $g1regex }
$xlist = $g1 |% { $_ -match 'x([\d.]+)' | Out-Null; [decimal] $Matches[1] } | Sort-Object
$ylist = $g1 |% { $_ -match 'y([\d.]+)' | Out-Null; [decimal] $Matches[1] } | Sort-Object
$xmin = $xlist[0]
$ymin = $ylist[0]
$xmax = $xlist[$xlist.Length-1]
$ymax = $ylist[$ylist.Length-1]
$w = $xlist[$xlist.Length-1] - $xlist[0]
$h = $ylist[$ylist.Length-1] - $ylist[0]
Write-Host $filename
Write-Host "xmin=$xmin ymin=$ymin xmax=$xmax ymax=$ymax"
Write-Host "w=$w h=$h"
Write-Host ''
$bmp = [System.Drawing.Bitmap]::new([int]($w*10) + 1, [int]($h*10) + 1)
$g1 |% {
$_ -match $g1regex | Out-Null
$x = [int](([decimal]$Matches[1] - $xmin) * 10)
$y = [int](($h-([decimal]$Matches[2] - $ymin)) * 10)
$bmp.SetPixel($x, $y, [System.Drawing.Color]::Black)
}
$bmp.Save($pngfilename)
return @{
xmin = $xmin
ymin = $ymin
xmax = $xmax
ymax = $ymax
w = $w
h = $h
}
}
#!/usr/bin/env python
"""
Modified by Jay Johnson 2015, J Tech Photonics, Inc., jtechphotonics.com
modified by Adam Polak 2014, polakiumengineering.org
based on Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru
based on gcode.py (C) 2007 hugomatic...
based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org
based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org
based on interp.py (C) 2005 Aaron Spike, aaron@ekips.org
based on bezmisc.py (C) 2005 Aaron Spike, aaron@ekips.org
based on cubicsuperpath.py (C) 2005 Aaron Spike, aaron@ekips.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import inkex
import simpletransform
import os
import math
import bezmisc
import re
import sys
import time
import numpy
import gettext
_ = gettext.gettext
# Deprecation hack. Access the formatStyle differently for inkscape >= 1.0
target_version = 1.0
if target_version < 1.0:
# simplestyle
import simplestyle
# etree
etree = inkex.etree
# cubicsuperpath
import cubicsuperpath
parsePath = cubicsuperpath.parsePath
# Inkex.Boolean
inkex.Boolean = bool
else:
# simplestyle
# Class and method names follow the old Inkscape API for compatibility's sake.
# When support is dropped for older versions this can be ganged to follow PEP 8.
class simplestyle(object): # noqa
# I think anonymous declarations would have been cleaner. However, Python 2 doesn't like how I use them
@staticmethod
def formatStyle(a): # noqa
return str(inkex.Style(a))
@staticmethod
def parseStyle(s): # noqa
return dict(inkex.Style.parse_str(s))
# etree
from lxml import etree # noqa
# cubicsuperpath
from inkex.paths import CubicSuperPath # noqa
parsePath = CubicSuperPath
# Check if inkex has error messages. (0.46 version does not have one) Could be removed later.
if "errormsg" not in dir(inkex):
inkex.errormsg = lambda msg: sys.stderr.write((str(msg) + "\n").encode("UTF-8"))
def bezierslopeatt(xxx_todo_changeme, t):
((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = xxx_todo_changeme
ax, ay, bx, by, cx, cy, x0, y0 = bezmisc.bezierparameterize(((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)))
dx = 3 * ax * (t ** 2) + 2 * bx * t + cx
dy = 3 * ay * (t ** 2) + 2 * by * t + cy
if dx == dy == 0:
dx = 6 * ax * t + 2 * bx
dy = 6 * ay * t + 2 * by
if dx == dy == 0:
dx = 6 * ax
dy = 6 * ay
if dx == dy == 0:
print_("Slope error x = %s*t^3+%s*t^2+%s*t+%s, y = %s*t^3+%s*t^2+%s*t+%s, t = %s, dx==dy==0" % (
ax, bx, cx, dx, ay, by, cy, dy, t))
print_(((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)))
dx, dy = 1, 1
return dx, dy
bezmisc.bezierslopeatt = bezierslopeatt
################################################################################
#
# Styles and additional parameters
#
################################################################################
math.pi2 = math.pi * 2
straight_tolerance = 0.0001
straight_distance_tolerance = 0.0001
engraving_tolerance = 0.0001
loft_lengths_tolerance = 0.0000001
options = {}
defaults = {
'header': """
G90
G0 Z50 F1200
G28 X0 Y0
G1 X80 Y120 F6000
G4 P0
G0 Z3 F1200
G4 P0
G1 X100 Y120 F2000
G4 P0
G0 Z4.5 F1200
G4 P0
G1 X80 Y122 F6000
G4 P0
G0 Z3 F1200
G4 P0
G1 X100 Y122 F2000
G4 P0
G0 Z4.5 F1200
G4 P0
G1 X80 Y124 F6000
G4 P0
G0 Z3 F1200
G4 P0
G1 X100 Y124 F2000
G4 P0
G0 Z4.5 F1200
G4 P0
""",
'footer': """
G0 X0 Y210 Z100
M18
"""
}
intersection_recursion_depth = 10
intersection_tolerance = 0.00001
styles = {
"loft_style": {
'main curve': simplestyle.formatStyle(
{'stroke': '#88f', 'fill': 'none', 'stroke-width': '1', 'marker-end': 'url(#Arrow2Mend)'}),
},
"biarc_style": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#88f', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#8f8', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'line': simplestyle.formatStyle(
{'stroke': '#f88', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'area': simplestyle.formatStyle(
{'stroke': '#777', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.1'}),
},
"biarc_style_dark": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#33a', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#3a3', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'line': simplestyle.formatStyle(
{'stroke': '#a33', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'area': simplestyle.formatStyle(
{'stroke': '#222', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_dark_area": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#33a', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.1'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#3a3', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.1'}),
'line': simplestyle.formatStyle(
{'stroke': '#a33', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.1'}),
'area': simplestyle.formatStyle(
{'stroke': '#222', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_i": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#880', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#808', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'line': simplestyle.formatStyle(
{'stroke': '#088', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'area': simplestyle.formatStyle(
{'stroke': '#999', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_dark_i": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#dd5', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#d5d', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'line': simplestyle.formatStyle(
{'stroke': '#5dd', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '1'}),
'area': simplestyle.formatStyle(
{'stroke': '#aaa', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_lathe_feed": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#07f', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#0f7', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'line': simplestyle.formatStyle(
{'stroke': '#f44', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'area': simplestyle.formatStyle(
{'stroke': '#aaa', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_lathe_passing feed": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#07f', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#0f7', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'line': simplestyle.formatStyle(
{'stroke': '#f44', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'area': simplestyle.formatStyle(
{'stroke': '#aaa', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"biarc_style_lathe_fine feed": {
'biarc0': simplestyle.formatStyle(
{'stroke': '#7f0', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'biarc1': simplestyle.formatStyle(
{'stroke': '#f70', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'line': simplestyle.formatStyle(
{'stroke': '#744', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '.4'}),
'area': simplestyle.formatStyle(
{'stroke': '#aaa', 'fill': 'none', "marker-end": "url(#DrawCurveMarker)", 'stroke-width': '0.3'}),
},
"area artefact": simplestyle.formatStyle({'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width': '1'}),
"area artefact arrow": simplestyle.formatStyle({'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width': '1'}),
"dxf_points": simplestyle.formatStyle({"stroke": "#ff0000", "fill": "#ff0000"}),
}
################################################################################
# Cubic Super Path additional functions
################################################################################
def csp_segment_to_bez(sp1, sp2):
return sp1[1:] + sp2[:2]
def csp_split(sp1, sp2, t=.5):
[x1, y1], [x2, y2], [x3, y3], [x4, y4] = sp1[1], sp1[2], sp2[0], sp2[1]
x12 = x1 + (x2 - x1) * t
y12 = y1 + (y2 - y1) * t
x23 = x2 + (x3 - x2) * t
y23 = y2 + (y3 - y2) * t
x34 = x3 + (x4 - x3) * t
y34 = y3 + (y4 - y3) * t
x1223 = x12 + (x23 - x12) * t
y1223 = y12 + (y23 - y12) * t
x2334 = x23 + (x34 - x23) * t
y2334 = y23 + (y34 - y23) * t
x = x1223 + (x2334 - x1223) * t
y = y1223 + (y2334 - y1223) * t
return [sp1[0], sp1[1], [x12, y12]], [[x1223, y1223], [x, y], [x2334, y2334]], [[x34, y34], sp2[1], sp2[2]]
def csp_curvature_at_t(sp1, sp2, t, depth=3):
ax, ay, bx, by, cx, cy, dx, dy = bezmisc.bezierparameterize(csp_segment_to_bez(sp1, sp2))
# curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5
f1x = 3 * ax * t ** 2 + 2 * bx * t + cx
f1y = 3 * ay * t ** 2 + 2 * by * t + cy
f2x = 6 * ax * t + 2 * bx
f2y = 6 * ay * t + 2 * by
d = (f1x ** 2 + f1y ** 2) ** 1.5
if d != 0:
return (f1x * f2y - f1y * f2x) / d
else:
t1 = f1x * f2y - f1y * f2x
if t1 > 0: return 1e100
if t1 < 0: return -1e100
# Use the Lapitals rule to solve 0/0 problem for 2 times...
t1 = 2 * (bx * ay - ax * by) * t + (ay * cx - ax * cy)
if t1 > 0: return 1e100
if t1 < 0: return -1e100
t1 = bx * ay - ax * by
if t1 > 0: return 1e100
if t1 < 0: return -1e100
if depth > 0:
# little hack ;^) hope it wont influence anything...
return csp_curvature_at_t(sp1, sp2, t * 1.004, depth - 1)
return 1e100
def csp_at_t(sp1, sp2, t):
ax, bx, cx, dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
ay, by, cy, dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]
x1, y1 = ax + (bx - ax) * t, ay + (by - ay) * t
x2, y2 = bx + (cx - bx) * t, by + (cy - by) * t
x3, y3 = cx + (dx - cx) * t, cy + (dy - cy) * t
x4, y4 = x1 + (x2 - x1) * t, y1 + (y2 - y1) * t
x5, y5 = x2 + (x3 - x2) * t, y2 + (y3 - y2) * t
x, y = x4 + (x5 - x4) * t, y4 + (y5 - y4) * t
return [x, y]
def cspseglength(sp1, sp2, tolerance=0.001):
bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:])
return bezmisc.bezierlength(bez, tolerance)
# Distance calculation from point to arc
def point_to_arc_distance(p, arc):
P0, P2, c, a = arc
dist = None
p = P(p)
r = (P0 - c).mag()
if r > 0:
i = c + (p - c).unit() * r
alpha = ((i - c).angle() - (P0 - c).angle())
if a * alpha < 0:
if alpha > 0:
alpha = alpha - math.pi2
else:
alpha = math.pi2 + alpha
if between(alpha, 0, a) or min(abs(alpha), abs(alpha - a)) < straight_tolerance:
return (p - i).mag(), (i.x, i.y)
else:
d1, d2 = (p - P0).mag(), (p - P2).mag()
if d1 < d2:
return (d1, (P0.x, P0.y))
else:
return (d2, (P2.x, P2.y))
def csp_to_arc_distance(sp1, sp2, arc1, arc2, tolerance=0.01): # arc = [start,end,center,alpha]
n, i = 10, 0
d, d1, dl = (0, (0, 0)), (0, (0, 0)), 0
while i < 1 or (abs(d1[0] - dl[0]) > tolerance and i < 4):
i += 1
dl = d1 * 1
for j in range(n + 1):
t = float(j) / n
p = csp_at_t(sp1, sp2, t)
d = min(point_to_arc_distance(p, arc1), point_to_arc_distance(p, arc2))
# inkex.debug("---Debug---")
# inkex.debug(str(d1) + str(d))
# inkex.debug(str(tuple(d1)) + str(tuple(d)))
d1 = max(tuple(d1), tuple(d))
n = n * 2
return d1[0]
################################################################################
# Common functions
################################################################################
def atan2(*arg):
if len(arg) == 1 and (type(arg[0]) == type([0., 0.]) or type(arg[0]) == type((0., 0.))):
return (math.pi / 2 - math.atan2(arg[0][0], arg[0][1])) % math.pi2
elif len(arg) == 2:
return (math.pi / 2 - math.atan2(arg[0], arg[1])) % math.pi2
else:
raise ValueError("Bad argumets for atan! (%s)" % arg)
def between(c, x, y):
return x - straight_tolerance <= c <= y + straight_tolerance or y - straight_tolerance <= c <= x + straight_tolerance
# Print arguments into specified log file
def print_(*arg):
f = open(options.log_filename, "a")
for s in arg:
s = str(str(s).encode('unicode_escape')) + " "
f.write(s)
f.write("\n")
f.close()
################################################################################
# Point (x,y) operations
################################################################################
class P:
def __init__(self, x, y=None):
if not y == None:
self.x, self.y = float(x), float(y)
else:
self.x, self.y = float(x[0]), float(x[1])
def __add__(self, other):
return P(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return P(self.x - other.x, self.y - other.y)
def __neg__(self):
return P(-self.x, -self.y)
def __mul__(self, other):
if isinstance(other, P):
return self.x * other.x + self.y * other.y
return P(self.x * other, self.y * other)
__rmul__ = __mul__
def __div__(self, other):
return P(self.x / other, self.y / other)
# Added to support python 3
__floordiv__ = __div__
__truediv__ = __div__
def mag(self):
return math.hypot(self.x, self.y)
def unit(self):
h = self.mag()
if h:
return self / h
else:
return P(0, 0)
def angle(self):
return math.atan2(self.y, self.x)
def __repr__(self):
return '%f,%f' % (self.x, self.y)
def l2(self):
return self.x * self.x + self.y * self.y
################################################################################
#
# Biarc function
#
# Calculates biarc approximation of cubic super path segment
# splits segment if needed or approximates it with straight line
#
################################################################################
def biarc(sp1, sp2, z1, z2, depth=0):
def biarc_split(sp1, sp2, z1, z2, depth):
if depth < options.biarc_max_split_depth:
sp1, sp2, sp3 = csp_split(sp1, sp2)
l1, l2 = cspseglength(sp1, sp2), cspseglength(sp2, sp3)
if l1 + l2 == 0:
zm = z1
else:
zm = z1 + (z2 - z1) * l1 / (l1 + l2)
return biarc(sp1, sp2, z1, zm, depth + 1) + biarc(sp2, sp3, zm, z2, depth + 1)
else:
return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]
P0, P4 = P(sp1[1]), P(sp2[1])
TS, TE, v = (P(sp1[2]) - P0), -(P(sp2[0]) - P4), P0 - P4
tsa, tea, va = TS.angle(), TE.angle(), v.angle()
if TE.mag() < straight_distance_tolerance and TS.mag() < straight_distance_tolerance:
# Both tangents are zerro - line straight
return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]
if TE.mag() < straight_distance_tolerance:
TE = -(TS + v).unit()
r = TS.mag() / v.mag() * 2
elif TS.mag() < straight_distance_tolerance:
TS = -(TE + v).unit()
r = 1 / (TE.mag() / v.mag() * 2)
else:
r = TS.mag() / TE.mag()
TS, TE = TS.unit(), TE.unit()
tang_are_parallel = (
(tsa - tea) % math.pi < straight_tolerance or math.pi - (tsa - tea) % math.pi < straight_tolerance)
if (tang_are_parallel and
((
v.mag() < straight_distance_tolerance or TE.mag() < straight_distance_tolerance or TS.mag() < straight_distance_tolerance) or
1 - abs(TS * v / (TS.mag() * v.mag())) < straight_tolerance)):
# Both tangents are parallel and start and end are the same - line straight
# or one of tangents still smaller then tollerance
# Both tangents and v are parallel - line straight
return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]
c, b, a = v * v, 2 * v * (r * TS + TE), 2 * r * (TS * TE - 1)
if v.mag() == 0:
return biarc_split(sp1, sp2, z1, z2, depth)
asmall, bsmall, csmall = abs(a) < 10 ** -10, abs(b) < 10 ** -10, abs(c) < 10 ** -10
if asmall and b != 0:
beta = -c / b
elif csmall and a != 0:
beta = -b / a
elif not asmall:
discr = b * b - 4 * a * c
if discr < 0: raise ValueError(a, b, c, discr)
disq = discr ** .5
beta1 = (-b - disq) / 2 / a
beta2 = (-b + disq) / 2 / a
if beta1 * beta2 > 0: raise ValueError(a, b, c, disq, beta1, beta2)
beta = max(beta1, beta2)
elif asmall and bsmall:
return biarc_split(sp1, sp2, z1, z2, depth)
alpha = beta * r
ab = alpha + beta
P1 = P0 + alpha * TS
P3 = P4 - beta * TE
P2 = (beta / ab) * P1 + (alpha / ab) * P3
def calculate_arc_params(P0, P1, P2):
D = (P0 + P2) / 2
if (D - P1).mag() == 0: return None, None
R = D - ((D - P0).mag() ** 2 / (D - P1).mag()) * (P1 - D).unit()
p0a, p1a, p2a = (P0 - R).angle() % (2 * math.pi), (P1 - R).angle() % (2 * math.pi), (P2 - R).angle() % (
2 * math.pi)
alpha = (p2a - p0a) % (2 * math.pi)
if (p0a < p2a and (p1a < p0a or p2a < p1a)) or (p2a < p1a < p0a):
alpha = -2 * math.pi + alpha
if abs(R.x) > 1000000 or abs(R.y) > 1000000 or (R - P0).mag() < .1:
return None, None
else:
return R, alpha
R1, a1 = calculate_arc_params(P0, P1, P2)
R2, a2 = calculate_arc_params(P2, P3, P4)
if R1 == None or R2 == None or (R1 - P0).mag() < straight_tolerance or (
R2 - P2).mag() < straight_tolerance: return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]
d = csp_to_arc_distance(sp1, sp2, [P0, P2, R1, a1], [P2, P4, R2, a2])
if d > 1 and depth < options.biarc_max_split_depth:
return biarc_split(sp1, sp2, z1, z2, depth)
else:
if R2.mag() * a2 == 0:
zm = z2
else:
zm = z1 + (z2 - z1) * (abs(R1.mag() * a1)) / (abs(R2.mag() * a2) + abs(R1.mag() * a1))
return [[sp1[1], 'arc', [R1.x, R1.y], a1, [P2.x, P2.y], [z1, zm]],
[[P2.x, P2.y], 'arc', [R2.x, R2.y], a2, [P4.x, P4.y], [zm, z2]]]
################################################################################
# Polygon class
################################################################################
class Polygon:
def __init__(self, polygon=None):
self.polygon = [] if polygon == None else polygon[:]
def add(self, add):
if type(add) == type([]):
self.polygon += add[:]
else:
self.polygon += add.polygon[:]
class ArrangementGenetic:
# gene = [fittness, order, rotation, xposition]
# spieces = [gene]*shapes count
# population = [spieces]
def __init__(self, polygons, material_width):
self.population = []
self.genes_count = len(polygons)
self.polygons = polygons
self.width = material_width
self.mutation_factor = 0.1
self.order_mutate_factor = 1.
self.move_mutate_factor = 1.
################################################################################
###
### Gcodetools class
###
################################################################################
class LaserGcode(inkex.Effect):
def export_gcode(self, gcode):
gcode_pass = gcode
for x in range(1, self.options.passes):
gcode += "G91\nG1 Z-" + self.options.pass_depth + "\nG90\n" + gcode_pass
f = open(self.options.directory + self.options.file, "w")
f.write(
self.header + self.options.laser_off_command + " S0" + "\n" +
"G1 F" + self.options.travel_speed + "\n" + gcode + self.footer)
f.close()
def add_arguments_old(self):
add_option = self.OptionParser.add_option
for arg in self.arguments:
# Stringify add_option arguments
action = arg["action"] if "action" in arg else "store"
arg_type = {str: "str", int: "int", bool: "inkbool"}[arg["type"]]
default = arg["type"](arg["default"])
add_option("", arg["name"], action=action, type=arg_type, dest=arg["dest"],
default=default, help=arg["help"])
def add_arguments_new(self):
add_argument = self.arg_parser.add_argument
for arg in self.arguments:
# Not using kwargs unpacking for clarity, flexibility and constancy with add_arguments_old
action = arg["action"] if "action" in arg else "store"
add_argument(arg["name"], action=action, type=arg["type"], dest=arg["dest"],
default=arg["default"], help=arg["help"])
def __init__(self):
inkex.Effect.__init__(self)
# Define command line arguments, inkex will use these to interface with the GUI defined in laser.ini
self.arguments = [
{"name": "--directory", "type": str, "dest": "directory",
"default": "", "help": "Output directory"},
{"name": "--filename", "type": str, "dest": "file",
"default": "output.gcode", "help": "File name"},
{"name": "--add-numeric-suffix-to-filename", "type": inkex.Boolean,
"dest": "add_numeric_suffix_to_filename", "default": False,
"help": "Add numeric suffix to file name"},
{"name": "--laser-command", "type": str, "dest": "laser_command",
"default": "M03", "help": "Laser gcode command"},
{"name": "--laser-off-command", "type": str, "dest": "laser_off_command",
"default": "M05", "help": "Laser gcode end command"},
{"name": "--laser-speed", "type": int, "dest": "laser_speed", "default": 750,
"help": "Laser speed (mm/min},"},
{"name": "--travel-speed", "type": str, "dest": "travel_speed",
"default": "3000", "help": "Travel speed (mm/min},"},
{"name": "--laser-power", "type": int, "dest": "laser_power", "default": 255,
"help": "S# is 256 or 10000 for full power"},
{"name": "--passes", "type": int, "dest": "passes", "default": 1,
"help": "Quantity of passes"},
{"name": "--pass-depth", "type": str, "dest": "pass_depth", "default": 1,
"help": "Depth of laser cut"},
{"name": "--power-delay", "type": str, "dest": "power_delay",
"default": "0", "help": "Laser power-on delay (ms},"},
{"name": "--suppress-all-messages", "type": inkex.Boolean,
"dest": "suppress_all_messages", "default": True,
"help": "Hide messages during g-code generation"},
{"name": "--create-log", "type": bool, "dest": "log_create_log",
"default": False, "help": "Create log files"},
{"name": "--log-filename", "type": str, "dest": "log_filename",
"default": '', "help": "Create log files"},
{"name": "--engraving-draw-calculation-paths", "type": inkex.Boolean,
"dest": "engraving_draw_calculation_paths", "default": False,
"help": "Draw additional graphics to debug engraving path"},
{"name": "--unit", "type": str, "dest": "unit",
"default": "G21 (All units in mm},", "help": "Units either mm or inches"},
{"name": "--active-tab", "type": str, "dest": "active_tab", "default": "",
"help": "Defines which tab is active"},
{"name": "--biarc-max-split-depth", "type": int,
"dest": "biarc_max_split_depth", "default": "4",
"help": "Defines maximum depth of splitting while approximating using biarcs."}
]
if target_version < 1.0:
self.add_arguments_old()
else:
self.add_arguments_new()
# Another hack to maintain support across different Inkscape versions
if target_version < 1.0:
self.selected_hack = self.selected
else:
self.selected_hack = self.svg.selected
def parse_curve(self, p, layer, w=None, f=None):
c = []
if len(p) == 0:
return []
p = self.transform_csp(p, layer)
# Sort to reduce Rapid distance
k = list(range(1, len(p)))
keys = [0]
while len(k) > 0:
end = p[keys[-1]][-1][1]
dist = (float('-inf'), float('-inf'))
for i in range(len(k)):
start = p[k[i]][0][1]
dist = max((-((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2), i), dist)
keys += [k[dist[1]]]
del k[dist[1]]
for k in keys:
subpath = p[k]
c += [[[subpath[0][1][0], subpath[0][1][1]], 'move', 0, 0]]
for i in range(1, len(subpath)):
sp1 = [[subpath[i - 1][j][0], subpath[i - 1][j][1]] for j in range(3)]
sp2 = [[subpath[i][j][0], subpath[i][j][1]] for j in range(3)]
c += biarc(sp1, sp2, 0, 0) if w == None else biarc(sp1, sp2, -f(w[k][i - 1]), -f(w[k][i]))
# l1 = biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
# print_((-f(w[k][i-1]),-f(w[k][i]), [i1[5] for i1 in l1]) )
c += [[[subpath[-1][1][0], subpath[-1][1][1]], 'end', 0, 0]]
print_("Curve: " + str(c))
return c
def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]):
self.get_defs()
# Add marker to defs if it does not exist
if "DrawCurveMarker" not in self.defs:
defs = etree.SubElement(self.document.getroot(), inkex.addNS("defs", "svg"))
marker = etree.SubElement(defs, inkex.addNS("marker", "svg"),
{"id": "DrawCurveMarker", "orient": "auto", "refX": "-8",
"refY": "-2.41063", "style": "overflow:visible"})
etree.SubElement(marker, inkex.addNS("path", "svg"),
{
"d": "m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
"style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"}
)
if "DrawCurveMarker_r" not in self.defs:
defs = etree.SubElement(self.document.getroot(), inkex.addNS("defs", "svg"))
marker = etree.SubElement(defs, inkex.addNS("marker", "svg"),
{"id": "DrawCurveMarker_r", "orient": "auto", "refX": "8",
"refY": "-2.41063", "style": "overflow:visible"})
etree.SubElement(marker, inkex.addNS("path", "svg"),
{
"d": "m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126",
"style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"}
)
for i in [0, 1]:
style['biarc%s_r' % i] = simplestyle.parseStyle(style['biarc%s' % i])
style['biarc%s_r' % i]["marker-start"] = "url(#DrawCurveMarker_r)"
del (style['biarc%s_r' % i]["marker-end"])
style['biarc%s_r' % i] = simplestyle.formatStyle(style['biarc%s_r' % i])
if group is None:
group = etree.SubElement(self.layers[min(1, len(self.layers) - 1)], inkex.addNS('g', 'svg'),
{"gcodetools": "Preview group"})
s, arcn = '', 0
a, b, c = [0., 0.], [1., 0.], [0., 1.]
k = (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])
a, b, c = self.transform(a, layer, True), self.transform(b, layer, True), self.transform(c, layer, True)
if ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])) * k > 0:
reverse_angle = 1
else:
reverse_angle = -1
for sk in curve:
si = sk[:]
si[0], si[2] = self.transform(si[0], layer, True), (
self.transform(si[2], layer, True) if type(si[2]) == type([]) and len(si[2]) == 2 else si[2])
if s != '':
if s[1] == 'line':
etree.SubElement(group, inkex.addNS('path', 'svg'),
{
'style': style['line'],
'd': 'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]),
"gcodetools": "Preview",
}
)
elif s[1] == 'arc':
arcn += 1
sp = s[0]
c = s[2]
s[3] = s[3] * reverse_angle
a = ((P(si[0]) - P(c)).angle() - (P(s[0]) - P(c)).angle()) % math.pi2 # s[3]
if s[3] * a < 0:
if a > 0:
a = a - math.pi2
else:
a = math.pi2 + a
r = math.sqrt((sp[0] - c[0]) ** 2 + (sp[1] - c[1]) ** 2)
a_st = (math.atan2(sp[0] - c[0], - (sp[1] - c[1])) - math.pi / 2) % (math.pi * 2)
st = style['biarc%s' % (arcn % 2)][:]
if a > 0:
a_end = a_st + a
st = style['biarc%s' % (arcn % 2)]
else:
a_end = a_st * 1
a_st = a_st + a
st = style['biarc%s_r' % (arcn % 2)]
etree.SubElement(group, inkex.addNS('path', 'svg'),
{
'style': st,
inkex.addNS('cx', 'sodipodi'): str(c[0]),
inkex.addNS('cy', 'sodipodi'): str(c[1]),
inkex.addNS('rx', 'sodipodi'): str(r),
inkex.addNS('ry', 'sodipodi'): str(r),
inkex.addNS('start', 'sodipodi'): str(a_st),
inkex.addNS('end', 'sodipodi'): str(a_end),
inkex.addNS('open', 'sodipodi'): 'true',
inkex.addNS('type', 'sodipodi'): 'arc',
"gcodetools": "Preview",
})
s = si
def check_dir(self):
if self.options.directory[-1] not in ["/", "\\"]:
if "\\" in self.options.directory:
self.options.directory += "\\"
else:
self.options.directory += "/"
print_("Checking direcrory: '%s'" % self.options.directory)
if (os.path.isdir(self.options.directory)):
if (os.path.isfile(self.options.directory + 'header')):
f = open(self.options.directory + 'header', 'r')
self.header = f.read()
f.close()
else:
self.header = defaults['header']
if (os.path.isfile(self.options.directory + 'footer')):
f = open(self.options.directory + 'footer', 'r')
self.footer = f.read()
f.close()
else:
self.footer = defaults['footer']
if self.options.unit == "G21 (All units in mm)":
self.header += "G21\n"
elif self.options.unit == "G20 (All units in inches)":
self.header += "G20\n"
else:
self.error(_("Directory does not exist! Please specify existing directory at options tab!"), "error")
return False
if self.options.add_numeric_suffix_to_filename:
dir_list = os.listdir(self.options.directory)
if "." in self.options.file:
r = re.match(r"^(.*)(\..*)$", self.options.file)
ext = r.group(2)
name = r.group(1)
else:
ext = ""
name = self.options.file
max_n = 0
for s in dir_list:
r = re.match(r"^%s_0*(\d+)%s$" % (re.escape(name), re.escape(ext)), s)
if r:
max_n = max(max_n, int(r.group(1)))
filename = name + "_" + ("0" * (4 - len(str(max_n + 1))) + str(max_n + 1)) + ext
self.options.file = filename
print_("Testing writing rights on '%s'" % (self.options.directory + self.options.file))
try:
f = open(self.options.directory + self.options.file, "w")
f.close()
except:
self.error(_("Can not write to specified file!\n%s" % (self.options.directory + self.options.file)),
"error")
return False
return True
################################################################################
#
# Generate Gcode
#
# Curve definition
# [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
#
################################################################################
def generate_gcode(self, curve, layer, depth):
tool = self.tools
print_("Tool in g-code generator: " + str(tool))
def c(c):
c = [c[i] if i < len(c) else None for i in range(6)]
if c[5] == 0: c[5] = None
s = [" X", " Y", " Z", " I", " J", " K"]
r = ''
for i in range(6):
if c[i] != None:
r += s[i] + ("%f" % (round(c[i], 4))).rstrip('0')
return r
if len(curve) == 0: return ""
try:
self.last_used_tool == None
except:
self.last_used_tool = None
print_("working on curve")
print_("Curve: " + str(curve))
g = ""
lg, f = 'G00', "F%f" % tool['penetration feed']
penetration_feed = "F%s" % tool['penetration feed']
current_a = 0
for i in range(1, len(curve)):
# Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
s, si = curve[i - 1], curve[i]
feed = f if lg not in ['G01', 'G02', 'G03'] else ''
if s[1] == 'move':
g += "G1 " + c(si[0]) + "\n" + tool['gcode before path'] + "\n"
lg = 'G00'
elif s[1] == 'end':
g += tool['gcode after path'] + "\n"
lg = 'G00'
elif s[1] == 'line':
if lg == "G00": g += "G1 " + feed + "\n"
g += "G1 " + c(si[0]) + "\n"
lg = 'G01'
elif s[1] == 'arc':
r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])]
if lg == "G00": g += "G1 " + feed + "\n"
if (r[0] ** 2 + r[1] ** 2) > .1:
r1, r2 = (P(s[0]) - P(s[2])), (P(si[0]) - P(s[2]))
if abs(r1.mag() - r2.mag()) < 0.001:
g += ("G2" if s[3] < 0 else "G3") + c(
si[0] + [None, (s[2][0] - s[0][0]), (s[2][1] - s[0][1])]) + "\n"
else:
r = (r1.mag() + r2.mag()) / 2
g += ("G2" if s[3] < 0 else "G3") + c(si[0]) + " R%f" % (r) + "\n"
lg = 'G02'
else:
g += "G1 " + c(si[0]) + " " + feed + "\n"
lg = 'G01'
if si[1] == 'end':
g += tool['gcode after path'] + "\n"
return g
def get_transforms(self, g):
root = self.document.getroot()
trans = []
while (g != root):
if 'transform' in list(g.keys()):
t = g.get('transform')
t = simpletransform.parseTransform(t)
trans = simpletransform.composeTransform(t, trans) if trans != [] else t
print_(trans)
g = g.getparent()
return trans
def apply_transforms(self, g, csp):
trans = self.get_transforms(g)
if trans != []:
simpletransform.applyTransformToPath(trans, csp)
return csp
def transform(self, source_point, layer, reverse=False):
if layer == None:
layer = self.current_layer if self.current_layer is not None else self.document.getroot()
if layer not in self.transform_matrix:
for i in range(self.layers.index(layer), -1, -1):
if self.layers[i] in self.orientation_points:
break
print_(str(self.layers))
print_(str("I: " + str(i)))
print_("Transform: " + str(self.layers[i]))
if self.layers[i] not in self.orientation_points:
self.error(_(
"Orientation points for '%s' layer have not been found! Please add orientation points using Orientation tab!") % layer.get(
inkex.addNS('label', 'inkscape')), "no_orientation_points")
elif self.layers[i] in self.transform_matrix:
self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
else:
orientation_layer = self.layers[i]
if len(self.orientation_points[orientation_layer]) > 1:
self.error(
_("There are more than one orientation point groups in '%s' layer") % orientation_layer.get(
inkex.addNS('label', 'inkscape')), "more_than_one_orientation_point_groups")
points = self.orientation_points[orientation_layer][0]
if len(points) == 2:
points += [[[(points[1][0][1] - points[0][0][1]) + points[0][0][0],
-(points[1][0][0] - points[0][0][0]) + points[0][0][1]],
[-(points[1][1][1] - points[0][1][1]) + points[0][1][0],
points[1][1][0] - points[0][1][0] + points[0][1][1]]]]
if len(points) == 3:
print_("Layer '%s' Orientation points: " % orientation_layer.get(inkex.addNS('label', 'inkscape')))
for point in points:
print_(point)
# Zcoordinates definition taken from Orientatnion point 1 and 2
self.Zcoordinates[layer] = [max(points[0][1][2], points[1][1][2]),
min(points[0][1][2], points[1][1][2])]
matrix = numpy.array([
[points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
[points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
[points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
])
if numpy.linalg.det(matrix) != 0:
m = numpy.linalg.solve(matrix,
numpy.array(
[[points[0][1][0]], [points[0][1][1]], [1], [points[1][1][0]],
[points[1][1][1]], [1], [points[2][1][0]], [points[2][1][1]], [1]]
)
).tolist()
self.transform_matrix[layer] = [[m[j * 3 + i][0] for i in range(3)] for j in range(3)]
else:
self.error(_(
"Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),
"wrong_orientation_points")
else:
self.error(_(
"Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),
"wrong_orientation_points")
self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()
print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label', 'inkscape')))
print_(self.transform_matrix)
print_(self.transform_matrix_reverse)
# Zautoscale is absolute
self.Zauto_scale[layer] = 1
print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer])
x, y = source_point[0], source_point[1]
if not reverse:
t = self.transform_matrix[layer]
else:
t = self.transform_matrix_reverse[layer]
return [t[0][0] * x + t[0][1] * y + t[0][2], t[1][0] * x + t[1][1] * y + t[1][2]]
def transform_csp(self, csp_, layer, reverse=False):
csp = [[[csp_[i][j][0][:], csp_[i][j][1][:], csp_[i][j][2][:]] for j in range(len(csp_[i]))] for i in
range(len(csp_))]
for i in range(len(csp)):
for j in range(len(csp[i])):
for k in range(len(csp[i][j])):
csp[i][j][k] = self.transform(csp[i][j][k], layer, reverse)
return csp
################################################################################
# Errors handling function, notes are just printed into Logfile,
# warnings are printed into log file and warning message is displayed but
# extension continues working, errors causes log and execution is halted
# Notes, warnings adn errors could be assigned to space or comma or dot
# sepparated strings (case is ignoreg).
################################################################################
def error(self, s, type_="Warning"):
notes = "Note "
warnings = """
Warning tools_warning
bad_orientation_points_in_some_layers
more_than_one_orientation_point_groups
more_than_one_tool
orientation_have_not_been_defined
tool_have_not_been_defined
selection_does_not_contain_paths
selection_does_not_contain_paths_will_take_all
selection_is_empty_will_comupe_drawing
selection_contains_objects_that_are_not_paths
"""
errors = """
Error
wrong_orientation_points
area_tools_diameter_error
no_tool_error
active_layer_already_has_tool
active_layer_already_has_orientation_points
"""
if type_.lower() in re.split("[\s\n,\.]+", errors.lower()):
print_(s)
inkex.errormsg(s + "\n")
sys.exit()
elif type_.lower() in re.split("[\s\n,\.]+", warnings.lower()):
print_(s)
if not self.options.suppress_all_messages:
inkex.errormsg(s + "\n")
elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()):
print_(s)
else:
print_(s)
inkex.errormsg(s)
sys.exit()
################################################################################
# Get defs from svg
################################################################################
def get_defs(self):
self.defs = {}
def recursive(g):
for i in g:
if i.tag == inkex.addNS("defs", "svg"):
for j in i:
self.defs[j.get("id")] = i
if i.tag == inkex.addNS("g", 'svg'):
recursive(i)
recursive(self.document.getroot())
################################################################################
#
# Get Gcodetools info from the svg
#
################################################################################
def get_info(self):
self.selected_paths = {}
self.paths = {}
self.orientation_points = {}
self.layers = [self.document.getroot()]
self.Zcoordinates = {}
self.transform_matrix = {}
self.transform_matrix_reverse = {}
self.Zauto_scale = {}
def recursive_search(g, layer, selected=False):
items = g.getchildren()
items.reverse()
for i in items:
if selected:
self.selected_hack[i.get("id")] = i
if i.tag == inkex.addNS("g", 'svg') and i.get(inkex.addNS('groupmode', 'inkscape')) == 'layer':
self.layers += [i]
recursive_search(i, i)
elif i.get('gcodetools') == "Gcodetools orientation group":
points = self.get_orientation_points(i)
if points != None:
self.orientation_points[layer] = self.orientation_points[layer] + [
points[:]] if layer in self.orientation_points else [points[:]]
print_("Found orientation points in '%s' layer: %s" % (
layer.get(inkex.addNS('label', 'inkscape')), points))
else:
self.error(_(
"Warning! Found bad orientation points in '%s' layer. Resulting Gcode could be corrupt!") % layer.get(
inkex.addNS('label', 'inkscape')), "bad_orientation_points_in_some_layers")
elif i.tag == inkex.addNS('path', 'svg'):
if "gcodetools" not in list(i.keys()):
self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
if i.get("id") in self.selected_hack:
self.selected_paths[layer] = self.selected_paths[layer] + [
i] if layer in self.selected_paths else [i]
elif i.tag == inkex.addNS("g", 'svg'):
recursive_search(i, layer, (i.get("id") in self.selected_hack))
elif i.get("id") in self.selected_hack:
self.error(_(
"This extension works with Paths and Dynamic Offsets and groups of them only! All other objects will be ignored!\nSolution 1: press Path->Object to path or Shift+Ctrl+C.\nSolution 2: Path->Dynamic offset or Ctrl+J.\nSolution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file."),
"selection_contains_objects_that_are_not_paths")
recursive_search(self.document.getroot(), self.document.getroot())
def get_orientation_points(self, g):
items = g.getchildren()
items.reverse()
p2, p3 = [], []
p = None
for i in items:
if i.tag == inkex.addNS("g", 'svg') and i.get("gcodetools") == "Gcodetools orientation point (2 points)":
p2 += [i]
if i.tag == inkex.addNS("g", 'svg') and i.get("gcodetools") == "Gcodetools orientation point (3 points)":
p3 += [i]
if len(p2) == 2:
p = p2
elif len(p3) == 3:
p = p3
if p == None: return None
points = []
for i in p:
point = [[], []]
for node in i:
if node.get('gcodetools') == "Gcodetools orientation point arrow":
point[0] = self.apply_transforms(node, parsePath(node.get("d")))[0][0][1]
if node.get('gcodetools') == "Gcodetools orientation point text":
r = re.match(
r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*',
node.text)
point[1] = [float(r.group(1)), float(r.group(2)), float(r.group(3))]
if point[0] != [] and point[1] != []: points += [point]
if len(points) == len(p2) == 2 or len(points) == len(p3) == 3:
return points
else:
return None
################################################################################
#
# dxfpoints
#
################################################################################
def dxfpoints(self):
if self.selected_paths == {}:
self.error(_(
"Noting is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."),
"warning")
for layer in self.layers:
if layer in self.selected_paths:
for path in self.selected_paths[layer]:
if self.options.dxfpoints_action == 'replace':
path.set("dxfpoint", "1")
r = re.match("^\s*.\s*(\S+)", path.get("d"))
if r != None:
print_(("got path=", r.group(1)))
path.set("d",
"m %s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z" % r.group(
1))
path.set("style", styles["dxf_points"])
if self.options.dxfpoints_action == 'save':
path.set("dxfpoint", "1")
if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
path.set("dxfpoint", "0")
################################################################################
#
# Laser
#
################################################################################
def laser(self):
def get_boundaries(points):
minx, miny, maxx, maxy = None, None, None, None
out = [[], [], [], []]
for p in points:
if minx == p[0]:
out[0] += [p]
if minx == None or p[0] < minx:
minx = p[0]
out[0] = [p]
if miny == p[1]:
out[1] += [p]
if miny == None or p[1] < miny:
miny = p[1]
out[1] = [p]
if maxx == p[0]:
out[2] += [p]
if maxx == None or p[0] > maxx:
maxx = p[0]
out[2] = [p]
if maxy == p[1]:
out[3] += [p]
if maxy == None or p[1] > maxy:
maxy = p[1]
out[3] = [p]
return out
def remove_duplicates(points):
i = 0
out = []
for p in points:
for j in range(i, len(points)):
if p == points[j]: points[j] = [None, None]
if p != [None, None]: out += [p]
i += 1
return (out)
def get_way_len(points):
l = 0
for i in range(1, len(points)):
l += math.sqrt((points[i][0] - points[i - 1][0]) ** 2 + (points[i][1] - points[i - 1][1]) ** 2)
return l
def sort_dxfpoints(points):
points = remove_duplicates(points)
ways = [
# l=0, d=1, r=2, u=3
[3, 0], # ul
[3, 2], # ur
[1, 0], # dl
[1, 2], # dr
[0, 3], # lu
[0, 1], # ld
[2, 3], # ru
[2, 1], # rd
]
minimal_way = []
minimal_len = None
minimal_way_type = None
for w in ways:
tpoints = points[:]
cw = []
for j in range(0, len(points)):
p = get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
tpoints.remove(p[0])
cw += p
curlen = get_way_len(cw)
if minimal_len == None or curlen < minimal_len:
minimal_len = curlen
minimal_way = cw
minimal_way_type = w
return minimal_way
if self.selected_paths == {}:
paths = self.paths
self.error(_("No paths are selected! Trying to work on all available paths."), "warning")
else:
paths = self.selected_paths
self.check_dir()
gcode = ""
biarc_group = etree.SubElement(
list(self.selected_paths.keys())[0] if len(list(self.selected_paths.keys())) > 0 else self.layers[0],
inkex.addNS('g', 'svg'))
print_(("self.layers=", self.layers))
print_(("paths=", paths))
for layer in self.layers:
if layer in paths:
print_(("layer", layer))
p = []
dxfpoints = []
for path in paths[layer]:
print_(str(layer))
if "d" not in list(path.keys()):
self.error(_(
"Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),
"selection_contains_objects_that_are_not_paths")
continue
csp = parsePath(path.get("d"))
csp = self.apply_transforms(path, csp)
if path.get("dxfpoint") == "1":
tmp_curve = self.transform_csp(csp, layer)
x = tmp_curve[0][0][0][0]
y = tmp_curve[0][0][0][1]
print_("got dxfpoint (scaled) at (%f,%f)" % (x, y))
dxfpoints += [[x, y]]
else:
p += csp
dxfpoints = sort_dxfpoints(dxfpoints)
curve = self.parse_curve(p, layer)
self.draw_curve(curve, layer, biarc_group)
gcode += self.generate_gcode(curve, layer, 0)
self.export_gcode(gcode)
################################################################################
#
# Orientation
#
################################################################################
def orientation(self, layer=None):
print_("entering orientations")
if layer == None:
layer = self.current_layer if self.current_layer is not None else self.document.getroot()
if layer in self.orientation_points:
self.error(_("Active layer already has orientation points! Remove them or select another layer!"),
"active_layer_already_has_orientation_points")
orientation_group = etree.SubElement(layer, inkex.addNS('g', 'svg'),
{"gcodetools": "Gcodetools orientation group"})
# translate == ['0', '-917.7043']
if layer.get("transform") != None:
translate = layer.get("transform").replace("translate(", "").replace(")", "").split(",")
else:
translate = [0, 0]
# doc height in pixels (38 mm == 143.62204724px)
doc_height = self.svg.unittouu(self.document.getroot().xpath('@height', namespaces=inkex.NSS)[0])
if self.document.getroot().get('height') == "100%":
doc_height = 1052.3622047
print_("Overriding height from 100 percents to %s" % doc_height)
print_("Document height: " + str(doc_height));
if self.options.unit == "G21 (All units in mm)":
points = [[0., 0., 0.], [100., 0., 0.], [0., 100., 0.]]
orientation_scale = 1
print_("orientation_scale < 0 ===> switching to mm units=%0.10f" % orientation_scale)
elif self.options.unit == "G20 (All units in inches)":
points = [[0., 0., 0.], [5., 0., 0.], [0., 5., 0.]]
orientation_scale = 90
print_("orientation_scale < 0 ===> switching to inches units=%0.10f" % orientation_scale)
points = points[:2]
print_(("using orientation scale", orientation_scale, "i=", points))
for i in points:
# X == Correct!
# si == x,y coordinate in px
# si have correct coordinates
# if layer have any transform it will be in translate so lets add that
si = [i[0] * orientation_scale, (i[1] * orientation_scale) + float(translate[1])]
g = etree.SubElement(orientation_group, inkex.addNS('g', 'svg'),
{'gcodetools': "Gcodetools orientation point (2 points)"})
etree.SubElement(g, inkex.addNS('path', 'svg'),
{
'style': "stroke:none;fill:#000000;",
'd': 'm %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z z' % (
si[0], -si[1] + doc_height),
'gcodetools': "Gcodetools orientation point arrow"
})
t = etree.SubElement(g, inkex.addNS('text', 'svg'),
{
'style': "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
inkex.addNS("space", "xml"): "preserve",
'x': str(si[0] + 10),
'y': str(-si[1] - 10 + doc_height),
'gcodetools': "Gcodetools orientation point text"
})
t.text = "(%s; %s; %s)" % (i[0], i[1], i[2])
################################################################################
#
# Effect
#
# Main function of Gcodetools class
#
################################################################################
def effect(self):
global options
options = self.options
options.self = self
options.doc_root = self.document.getroot()
# define print_ function
global print_
if self.options.log_create_log:
try:
if os.path.isfile(self.options.log_filename): os.remove(self.options.log_filename)
f = open(self.options.log_filename, "a")
f.write("Gcodetools log file.\nStarted at %s.\n%s\n" % (
time.strftime("%d.%m.%Y %H:%M:%S"), options.log_filename))
f.write("%s tab is active.\n" % self.options.active_tab)
f.close()
except:
print_ = lambda *x: None
else:
print_ = lambda *x: None
self.get_info()
if self.orientation_points == {}:
self.error(_(
"Orientation points have not been defined! A default set of orientation points has been automatically added."),
"warning")
self.orientation(self.layers[min(0, len(self.layers) - 1)])
self.get_info()
self.tools = {
"name": "Laser Engraver",
"id": "Laser Engraver",
"penetration feed": self.options.laser_speed,
"feed": self.options.laser_speed,
"gcode before path": ("G4 P0 \n" + self.options.laser_command + " S" + str(
int(self.options.laser_power)) + "\nG4 P" + self.options.power_delay),
"gcode after path": (
"G4 P0 \n" + self.options.laser_off_command + " S0" + "\n" + "G1 F" + self.options.travel_speed),
}
self.get_info()
self.laser()
e = LaserGcode()
if target_version < 1.0:
e.affect()
else:
e.run()
$ErrorActionPreference = 'Stop'
$filename = 'ventilation-controller.drl'
[decimal]$zadj = 0.5
[decimal]$targetx = 34.9
[decimal]$targety = 48.6
$xmin = 62.23
$ymin = 57.15
$xmax = 138.43
$ymax = 105.41
. ./analyze-gcode.ps1
$toolDefRegex = '^t([\d]+)c([\d.]+)$'
$toolChangeRegex = '^t([\d]+)$'
$holeRegex = '^x([\d.]+)y([\d.]+)$'
$newfilename = '{0}_drill.gcode' -f [System.IO.Path]::GetFileNameWithoutExtension($filename)
$drl = Get-Content $filename
$xadj = $targetx - $xmin
$yadj = $targety - $ymin
$targetxmax = $xmax + $xadj
$targetymax = $ymax + $yadj
Write-Host "board xmin=$targetx ymin=$targety xmax=$targetxmax ymax=$targetymax"
Write-Host "G0 X$targetx Y$targety"
Write-Host "G0 X$targetxmax Y$targetymax"
[decimal]$loweredz = 3 + $zadj
[decimal]$raisedz = $loweredz + 1.5
$lowerPen = "G0 Z$loweredz F1200"
$raisePen = "G0 Z$raisedz F1200"
$wait = 'G4 P0'
$header = @"
G90
G0 Z50 F1200
G28 X0 Y0
"@
$footer += @"
G0 X0 Y210 Z100
M18
"@
$extent = @"
; {0} {1} {2} {3} {4} {5}
G1 F6000
G1 X{0} Y{1}
$wait
$lowerPen
$wait
G1 F2000
G1 X{2} Y{3}
$wait
G1 X{4} Y{5}
$wait
$raisePen
$wait
"@
$xpattern = @"
; {0} {1} {2} {3}
G1 F6000
G1 X{0} Y{1}
$wait
$lowerPen
$wait
G1 F2000
G1 X{2} Y{3}
$wait
$raisePen
$wait
G1 F6000
G1 X{0} Y{3}
$wait
$lowerPen
$wait
G1 F2000
G1 X{2} Y{1}
$wait
$raisePen
$wait
"@
$tools = @{}
$result = $header
$result += $extent -f ($targetx + 10), $targety, $targetx, $targety, $targetx, ($targety + 10)
$result += $extent -f ($targetxmax - 10), $targetymax, $targetxmax, $targetymax, $targetxmax, ($targetymax - 10)
$drl |% {
if ($_ -match $toolDefRegex) {
$index = [int]$Matches[1];
$diameter = [decimal]$Matches[2];
$tools.$index = $diameter
return
}
if ($_ -match $toolChangeRegex) {
$index = [int]$Matches[1];
$tools.current = $tools.$index
}
if ($_ -match $holeRegex) {
$x = [decimal]$Matches[1] + $xadj;
$y = [decimal]$Matches[2] + $yadj;
if (-not $tools.current){
Write-Error 'Tool not set'
exit
}
$r = $tools.current / 2
$result += $xpattern -f ($x-$r), ($y-$r), ($x+$r), ($y+$r)
return
}
}
$result += $footer
$result | Set-Content $newfilename
Analyze-Gcode $newfilename | Out-Null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment