add Lightburn .lbrn2 file format support

This commit is contained in:
suks.ae 2022-03-27 10:40:38 +02:00 committed by Florian Festi
parent 1362364c33
commit e8b88e53f1
3 changed files with 201 additions and 6 deletions

View File

@ -1279,7 +1279,7 @@ class Boxes:
self.corner(-a)
@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
@ -1315,7 +1315,7 @@ class Boxes:
raise ValueError("Unknown alignment: %s" % align)
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)
tx_sizes = {
@ -1879,7 +1879,7 @@ class Boxes:
if 2*i in ignore_widths:
l += edges[i+1].startwidth()
e2 = self.edges["e"]
if 2*i+1in ignore_widths:
if 2*i+1 in ignore_widths:
e1 = self.edges["e"]
edges[i](l,

View File

@ -718,7 +718,197 @@ showpage
)
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

View File

@ -19,18 +19,19 @@ import subprocess
import tempfile
import os
import shutil
from boxes.drawing import SVGSurface, PSSurface, Context
from boxes.drawing import SVGSurface, PSSurface, LBRN2Surface, Context
class Formats:
pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"]
_BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps']
_BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps', 'lbrn2']
formats = {
"svg": None,
"svg_Ponoko": None,
"ps": None,
"lbrn2": None,
"dxf": "-flat 0.1 -f dxf:-mm".split(),
"gcode": "-f gcode".split(),
"plt": "-f plot-hpgl".split(),
@ -42,6 +43,7 @@ class Formats:
"svg": [('Content-type', 'image/svg+xml; charset=utf-8')],
"svg_Ponoko": [('Content-type', 'image/svg+xml; charset=utf-8')],
"ps": [('Content-type', 'application/postscript')],
"lbrn2": [('Content-type', 'application/lbrn2')],
"dxf": [('Content-type', 'image/vnd.dxf')],
"plt": [('Content-type', ' application/vnd.hp-hpgl')],
"gcode": [('Content-type', 'text/plain; charset=utf-8')],
@ -63,6 +65,9 @@ class Formats:
def getSurface(self, fmt, filename):
if fmt in ("svg", "svg_Ponoko"):
surface = SVGSurface(filename)
else:
if fmt == "lbrn2":
surface = LBRN2Surface(filename)
else:
surface = PSSurface(filename)