add Lightburn .lbrn2 file format support
This commit is contained in:
parent
1362364c33
commit
e8b88e53f1
|
@ -1279,7 +1279,7 @@ class Boxes:
|
||||||
self.corner(-a)
|
self.corner(-a)
|
||||||
|
|
||||||
@restore
|
@restore
|
||||||
def text(self, text, x=0, y=0, angle=0, align="", fontsize=10, color=[0.0, 0.0, 0.0]):
|
def text(self, text, x=0, y=0, angle=0, align="", fontsize=10, color=[0.0, 0.0, 0.0], font="Arial"):
|
||||||
"""
|
"""
|
||||||
Draw text
|
Draw text
|
||||||
|
|
||||||
|
@ -1315,7 +1315,7 @@ class Boxes:
|
||||||
raise ValueError("Unknown alignment: %s" % align)
|
raise ValueError("Unknown alignment: %s" % align)
|
||||||
|
|
||||||
for line in reversed(text):
|
for line in reversed(text):
|
||||||
self.ctx.show_text(line, fs=fontsize, align=halign, rgb=color)
|
self.ctx.show_text(line, fs=fontsize, align=halign, rgb=color, font=font)
|
||||||
self.moveTo(0, 1.4 * fontsize)
|
self.moveTo(0, 1.4 * fontsize)
|
||||||
|
|
||||||
tx_sizes = {
|
tx_sizes = {
|
||||||
|
@ -1879,7 +1879,7 @@ class Boxes:
|
||||||
if 2*i in ignore_widths:
|
if 2*i in ignore_widths:
|
||||||
l += edges[i+1].startwidth()
|
l += edges[i+1].startwidth()
|
||||||
e2 = self.edges["e"]
|
e2 = self.edges["e"]
|
||||||
if 2*i+1in ignore_widths:
|
if 2*i+1 in ignore_widths:
|
||||||
e1 = self.edges["e"]
|
e1 = self.edges["e"]
|
||||||
|
|
||||||
edges[i](l,
|
edges[i](l,
|
||||||
|
|
190
boxes/drawing.py
190
boxes/drawing.py
|
@ -718,7 +718,197 @@ showpage
|
||||||
)
|
)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
class LBRN2Surface(Surface):
|
||||||
|
|
||||||
|
invert_y = False
|
||||||
|
dbg = False
|
||||||
|
|
||||||
|
fonts = {
|
||||||
|
'serif' : 'Times New Roman',
|
||||||
|
'sans-serif' : 'Arial',
|
||||||
|
'monospaced' : 'Courier New'
|
||||||
|
}
|
||||||
|
|
||||||
|
def finish(self, inner_corners="loop"):
|
||||||
|
if self.dbg: print("LBRN2 save")
|
||||||
|
extents = self._adjust_coordinates()
|
||||||
|
w = extents.width * self.scale
|
||||||
|
h = extents.height * self.scale
|
||||||
|
|
||||||
|
svg = ET.Element('LightBurnProject', AppVersion="1.0.06", FormatVersion="1", MaterialHeight="0", MirrorX="False", MirrorY="False")
|
||||||
|
svg.text = "\n"
|
||||||
|
num = 0
|
||||||
|
txtOffset = {}
|
||||||
|
|
||||||
|
tree = ET.ElementTree(svg)
|
||||||
|
if self.dbg: print ("8", num)
|
||||||
|
|
||||||
|
for i, part in enumerate(self.parts):
|
||||||
|
if self.dbg: print ("7", num)
|
||||||
|
if not part.pathes:
|
||||||
|
continue
|
||||||
|
for j, path in enumerate(part.pathes):
|
||||||
|
Color = 2*int(path.params["rgb"][0])+4*int(path.params["rgb"][1])+int(path.params["rgb"][2])
|
||||||
|
if Color == 4: # 4 is yellow in Lightburn
|
||||||
|
Color = 3 # use green instead
|
||||||
|
p = []
|
||||||
|
x, y = 0, 0
|
||||||
|
C = ""
|
||||||
|
start = None
|
||||||
|
last = None
|
||||||
|
path.faster_edges(inner_corners)
|
||||||
|
num = 0
|
||||||
|
cnt = 1
|
||||||
|
|
||||||
|
ende = len(path.path)-1
|
||||||
|
if self.dbg:
|
||||||
|
for c in path.path:
|
||||||
|
print ("6",num, c)
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
num = 0
|
||||||
|
c = path.path[num]
|
||||||
|
C, x, y = c[0:3]
|
||||||
|
if self.dbg: print("ende:" ,ende)
|
||||||
|
while num < ende or (C == "T" and num <= ende): #len(path.path):
|
||||||
|
if self.dbg: print ("0", num)
|
||||||
|
c = path.path[num]
|
||||||
|
if self.dbg: print("first: ", num, c)
|
||||||
|
|
||||||
|
C, x, y = c[0:3]
|
||||||
|
if C == "M":
|
||||||
|
if self.dbg: print ("1", num)
|
||||||
|
sh = ET.SubElement(svg, "Shape", Type="Path", CutIndex=str(Color))
|
||||||
|
sh.text = "\n "
|
||||||
|
sh.tail = "\n"
|
||||||
|
vl = ET.SubElement(sh, "VertList")
|
||||||
|
vl.text = f"V{x:.3f} {y:.3f}c0x1c1x1"
|
||||||
|
vl.tail = "\n"
|
||||||
|
pl = ET.SubElement(sh, "PrimList")
|
||||||
|
pl.text = ""#f"L{cnt} {cnt+1}"
|
||||||
|
pl.tail = "\n"
|
||||||
|
start = c
|
||||||
|
x0, y0 = x, y
|
||||||
|
# do something with M
|
||||||
|
done = False
|
||||||
|
bspline = False
|
||||||
|
while done == False and num < ende: #len(path.path):
|
||||||
|
num += 1
|
||||||
|
c = path.path[num]
|
||||||
|
if self.dbg: print ("next: ",num, c)
|
||||||
|
C, x, y = c[0:3]
|
||||||
|
if C == "M":
|
||||||
|
if start and points_equal(start[1], start[2], x, y):
|
||||||
|
pl.text = "LineClosed"
|
||||||
|
start = c
|
||||||
|
cnt = 1
|
||||||
|
if self.dbg: print ("next, because M")
|
||||||
|
done = True
|
||||||
|
elif C == "T":
|
||||||
|
if self.dbg: print ("next, because T")
|
||||||
|
done = True
|
||||||
|
else:
|
||||||
|
if C == "L":
|
||||||
|
vl.text+=(f"V{x:.3f} {y:.3f}c0x1c1x1")
|
||||||
|
pl.text += f"L{cnt-1} {cnt}"
|
||||||
|
cnt +=1
|
||||||
|
elif C == "C":
|
||||||
|
x1, y1, x2, y2 = c[3:]
|
||||||
|
if self.dbg: print ("C: ",x0, y0, x1, y1, x, y, x2, y2)
|
||||||
|
vl.text+=(f"V{x0:.3f} {y0:.3f}c0x{(x1):.3f}c0y{(y1):.3f}c1x1V{x:.3f} {y:.3f}c0x1c1x{(x2):.3f}c1y{(y2):.3f}")
|
||||||
|
pl.text += f"L{cnt-1} {cnt}B{cnt} {cnt+1}"
|
||||||
|
cnt +=2
|
||||||
|
bspline = True
|
||||||
|
else:
|
||||||
|
print("unknown", c)
|
||||||
|
if done == False:
|
||||||
|
x0, y0 = x, y
|
||||||
|
|
||||||
|
if start and points_equal(start[1], start[2], x0, y0):
|
||||||
|
if bspline == False:
|
||||||
|
pl.text = "LineClosed"
|
||||||
|
start = c
|
||||||
|
if self.dbg: print ("2", num)
|
||||||
|
elif C == "T":
|
||||||
|
cnt = 1
|
||||||
|
#C = ""
|
||||||
|
if self.dbg: print ("3", num)
|
||||||
|
m, text, params = c[3:]
|
||||||
|
m = m * Affine.translation(0, params['fs'])
|
||||||
|
if self.dbg: print ("T: ",x, y, c)
|
||||||
|
num += 1
|
||||||
|
font, bold, italic = params['ff']
|
||||||
|
if params.get('font', 'Arial')=='Arial':
|
||||||
|
f = self.fonts[font]
|
||||||
|
else:
|
||||||
|
f = params.get('font', 'Arial')
|
||||||
|
fontColor = 2*int(params['rgb'][0])+4*int(params['rgb'][1])+int(params['rgb'][2])
|
||||||
|
if fontColor == 4: # 4 is yellow in Lightburn
|
||||||
|
fontColor = 3 # use green instead
|
||||||
|
|
||||||
|
#alignment can be left|middle|end
|
||||||
|
if params.get('align', 'left')=='middle':
|
||||||
|
hor = '1'
|
||||||
|
else:
|
||||||
|
if params.get('align', 'left')=='end':
|
||||||
|
hor = '2'
|
||||||
|
else:
|
||||||
|
hor = '0'
|
||||||
|
ver = 2 # vertical is always bottom, text is shifted in box class
|
||||||
|
|
||||||
|
pos = text.find('%')
|
||||||
|
offs = 0
|
||||||
|
if pos >- 1:
|
||||||
|
if self.dbg: print ("p: ", pos, text[pos+1:pos+3])
|
||||||
|
texttype = '2'
|
||||||
|
if self.dbg: print("l ", len(text[pos+1:pos+3]))
|
||||||
|
if text[pos+1:pos+2].isnumeric():
|
||||||
|
if self.dbg: print ("t0", text[pos+1:pos+3])
|
||||||
|
if text[pos+1:pos+3].isnumeric() and len(text[pos+1:pos+3]) == 2:
|
||||||
|
if self.dbg: print ("t1")
|
||||||
|
if text[pos:pos+3] in txtOffset:
|
||||||
|
if self.dbg: print ("t2")
|
||||||
|
offs = txtOffset[text[pos:pos+3]] + 1
|
||||||
|
else:
|
||||||
|
if self.dbg: print ("t3")
|
||||||
|
offs = 0
|
||||||
|
txtOffset[text[pos:pos+3]] = offs
|
||||||
|
else:
|
||||||
|
if self.dbg: print ("t4")
|
||||||
|
if text[pos:pos+2] in txtOffset:
|
||||||
|
if self.dbg: print ("t5")
|
||||||
|
offs = txtOffset[text[pos:pos+2]] + 1
|
||||||
|
else:
|
||||||
|
offs = 0
|
||||||
|
if self.dbg: print ("t6")
|
||||||
|
txtOffset[text[pos:pos+2]] = offs
|
||||||
|
else:
|
||||||
|
if self.dbg: print ("t7")
|
||||||
|
texttype = '0'
|
||||||
|
else:
|
||||||
|
texttype = '0'
|
||||||
|
if self.dbg: print ("t8")
|
||||||
|
if self.dbg: print ("o: ", text, txtOffset, offs)
|
||||||
|
|
||||||
|
sh = ET.SubElement(svg, "Shape", Type="Text", CutIndex=str(fontColor), Font=f"{f}", H=f"{(params['fs']*1.75*0.6086434):.3f}", Str=f"{text}", Bold=f"{'1' if bold else '0'}", Italic=f"{'1' if italic else '0'}", Ah=f"{str(hor)}", Av=f"{str(ver)}", Eval=f"{texttype}", VariableOffset=f"{str(offs)}") # 1mm = 1.75 Lightburn H units
|
||||||
|
sh.text = "\n "
|
||||||
|
sh.tail = "\n"
|
||||||
|
xf = ET.SubElement(sh, "XForm")
|
||||||
|
xf.text = " ".join((f"{m[i]:.3f}" for i in (0, 3, 1, 4, 2, 5)))
|
||||||
|
xf.tail = "\n"
|
||||||
|
else:
|
||||||
|
if self.dbg: print ("4", num)
|
||||||
|
print ("next, because not M")
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
url = self.metadata["url"].replace("&render=1", "") # remove render argument to get web form again
|
||||||
|
|
||||||
|
pl = ET.SubElement(svg, "Notes", ShowOnLoad="1", Notes="File created by Boxes.py script, programmed by Florian Festi.\nLightburn output by Klaus Steinhammer.\n\nURL with settings:\n" + str(url))
|
||||||
|
pl.text = ""
|
||||||
|
pl.tail = "\n"
|
||||||
|
|
||||||
|
if self.dbg: print ("5", num)
|
||||||
|
tree.write(open(self._fname, "wb"), xml_declaration=True, method="xml", encoding="UTF-8")
|
||||||
from random import random
|
from random import random
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,18 +19,19 @@ import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from boxes.drawing import SVGSurface, PSSurface, Context
|
from boxes.drawing import SVGSurface, PSSurface, LBRN2Surface, Context
|
||||||
|
|
||||||
class Formats:
|
class Formats:
|
||||||
|
|
||||||
pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"]
|
pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"]
|
||||||
|
|
||||||
_BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps']
|
_BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps', 'lbrn2']
|
||||||
|
|
||||||
formats = {
|
formats = {
|
||||||
"svg": None,
|
"svg": None,
|
||||||
"svg_Ponoko": None,
|
"svg_Ponoko": None,
|
||||||
"ps": None,
|
"ps": None,
|
||||||
|
"lbrn2": None,
|
||||||
"dxf": "-flat 0.1 -f dxf:-mm".split(),
|
"dxf": "-flat 0.1 -f dxf:-mm".split(),
|
||||||
"gcode": "-f gcode".split(),
|
"gcode": "-f gcode".split(),
|
||||||
"plt": "-f plot-hpgl".split(),
|
"plt": "-f plot-hpgl".split(),
|
||||||
|
@ -42,6 +43,7 @@ class Formats:
|
||||||
"svg": [('Content-type', 'image/svg+xml; charset=utf-8')],
|
"svg": [('Content-type', 'image/svg+xml; charset=utf-8')],
|
||||||
"svg_Ponoko": [('Content-type', 'image/svg+xml; charset=utf-8')],
|
"svg_Ponoko": [('Content-type', 'image/svg+xml; charset=utf-8')],
|
||||||
"ps": [('Content-type', 'application/postscript')],
|
"ps": [('Content-type', 'application/postscript')],
|
||||||
|
"lbrn2": [('Content-type', 'application/lbrn2')],
|
||||||
"dxf": [('Content-type', 'image/vnd.dxf')],
|
"dxf": [('Content-type', 'image/vnd.dxf')],
|
||||||
"plt": [('Content-type', ' application/vnd.hp-hpgl')],
|
"plt": [('Content-type', ' application/vnd.hp-hpgl')],
|
||||||
"gcode": [('Content-type', 'text/plain; charset=utf-8')],
|
"gcode": [('Content-type', 'text/plain; charset=utf-8')],
|
||||||
|
@ -63,6 +65,9 @@ class Formats:
|
||||||
def getSurface(self, fmt, filename):
|
def getSurface(self, fmt, filename):
|
||||||
if fmt in ("svg", "svg_Ponoko"):
|
if fmt in ("svg", "svg_Ponoko"):
|
||||||
surface = SVGSurface(filename)
|
surface = SVGSurface(filename)
|
||||||
|
else:
|
||||||
|
if fmt == "lbrn2":
|
||||||
|
surface = LBRN2Surface(filename)
|
||||||
else:
|
else:
|
||||||
surface = PSSurface(filename)
|
surface = PSSurface(filename)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue