add brother_ql setting to inventory_sticker
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
<param name="export_png" type="bool" gui-text="Export PNG">false</param>
|
||||
<param name="print_png" type="int" gui-text="Print PNG to Brother QL-720NW (count)" gui-description="Enter desired amount of stickers to print for each Id">0</param>
|
||||
<param name="print_device" type="string" gui-text="Printer interface (USB)" gui-description="[VendorID:ProductID], Example: 04f9:2044">04f9:2044</param>
|
||||
<param name="brother_ql" type="path" gui-text="brother_ql command" gui-description="The path of brother_ql executable" mode="file">/home/myprofile/venv/bin/brother_ql</param>
|
||||
<param name="preview" type="bool" gui-text="Generate preview only" gui-description="If enabled stickers will not be exported. Just generate sticker for the first given Id">false</param>
|
||||
</page>
|
||||
<page name="tab_about" gui-text="About">
|
||||
@@ -40,7 +41,7 @@
|
||||
<page name="tab_donate" gui-text="Donate">
|
||||
<label appearance="header">Coffee + Pizza</label>
|
||||
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
|
||||
<spacer/>
|
||||
<spacer/>
|
||||
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
|
||||
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
|
||||
<spacer/>
|
||||
@@ -55,10 +56,10 @@
|
||||
<submenu name="Cutting/Plotting/Printing"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
<menu-tip>This extension generates inventory stickers for thermo printers (we use Brother QL-720NW) from a provided web server *.csv file</menu-tip>
|
||||
<icon>icon.svg</icon>
|
||||
<menu-tip>This extension generates inventory stickers for thermo printers (we use Brother QL-720NW) from a provided web server *.csv file</menu-tip>
|
||||
<icon>icon.svg</icon>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">inventory_sticker.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
||||
</inkscape-extension>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# An extension to generate SVG/PNG labels (stickers) for use with our item inventory system.
|
||||
# An extension to generate SVG/PNG labels (stickers) for use with our item inventory system.
|
||||
# It pulls a .csv file from a server URL (protected by basic auth) and exports and prints the labels to a Brother QL-720NW label printer
|
||||
# Documentation: https://wiki.fablabchemnitz.de/display/TEED/Werkstattorientierung+im+FabLab+-+Digtales+Inventar
|
||||
#
|
||||
@@ -357,7 +357,7 @@ def splitAt(string, length):
|
||||
return ' '.join(string[i:i+length] for i in range(0,len(string),length))
|
||||
|
||||
class InventorySticker(inkex.Effect):
|
||||
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--tab")
|
||||
pars.add_argument("--server_address", default="https://the.domain.de/items.csv")
|
||||
@@ -371,9 +371,10 @@ class InventorySticker(inkex.Effect):
|
||||
pars.add_argument("--preview", type=inkex.Boolean, default=False)
|
||||
pars.add_argument("--export_svg", type=inkex.Boolean, default=True)
|
||||
pars.add_argument("--export_png", type=inkex.Boolean, default=False)
|
||||
pars.add_argument("--print_png", type=int, default=0)
|
||||
pars.add_argument("--print_device", default="04f9:2044")
|
||||
|
||||
pars.add_argument("--print_png", type=int, default=0)
|
||||
pars.add_argument("--print_device", default="04f9:2044")
|
||||
pars.add_argument("--brother_ql", default="/home/myprofile/venv/bin/brother_ql")
|
||||
|
||||
def effect(self):
|
||||
globalFont = "Miso"
|
||||
misoAvailable = False
|
||||
@@ -384,12 +385,12 @@ class InventorySticker(inkex.Effect):
|
||||
break
|
||||
if misoAvailable is False:
|
||||
inkex.errormsg("Warning: " + globalFont + " Font could not be found. Did you properly install the font? Please note: Stickers will look malformed!")
|
||||
|
||||
|
||||
# Adjust the document view for the desired sticker size
|
||||
root = self.svg.getElement("//svg:svg")
|
||||
|
||||
subline_fontsize = 40 #px; one line of bottom text (id and owner) creates a box of that height
|
||||
|
||||
|
||||
#our DataMatrix has size 16x16, each cube is sized by 16x16px -> total size is 256x256px. We use 4px padding for all directions
|
||||
DataMatrix_xy = 16
|
||||
DataMatrix_height = 16 * DataMatrix_xy
|
||||
@@ -397,12 +398,12 @@ class InventorySticker(inkex.Effect):
|
||||
sticker_padding = 4
|
||||
sticker_height = DataMatrix_height + subline_fontsize + 3 * sticker_padding
|
||||
sticker_width = 696
|
||||
|
||||
|
||||
#configure font sizes and box heights to define how large the font size may be at maximum (to omit overflow)
|
||||
objectNameMaxHeight = sticker_height - 2 * subline_fontsize - 4 * sticker_padding
|
||||
objectNameMaxLines = 5
|
||||
objectNameFontSize = objectNameMaxHeight / objectNameMaxLines #px; generate main font size from lines and box size
|
||||
|
||||
|
||||
root.set("width", str(sticker_width) + "px")
|
||||
root.set("height", str(sticker_height) + "px")
|
||||
root.set("viewBox", "%f %f %f %f" % (0, 0, sticker_width, sticker_height))
|
||||
@@ -411,7 +412,7 @@ class InventorySticker(inkex.Effect):
|
||||
for node in self.document.xpath('//*', namespaces=inkex.NSS):
|
||||
if node.TAG not in ('svg', 'defs', 'namedview'):
|
||||
node.delete()
|
||||
|
||||
|
||||
#set the document units
|
||||
self.document.getroot().find(inkex.addNS("namedview", "sodipodi")).set("inkscape:document-units", "px")
|
||||
|
||||
@@ -420,33 +421,33 @@ class InventorySticker(inkex.Effect):
|
||||
password_mgr.add_password(None, self.options.server_address, self.options.htuser, self.options.htpassword)
|
||||
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
|
||||
opener = urllib.request.build_opener(handler)
|
||||
|
||||
|
||||
try:
|
||||
inventoryData = opener.open(self.options.server_address).read().decode("utf-8")
|
||||
urllib.request.install_opener(opener)
|
||||
|
||||
inventoryCSVParent = os.path.join(self.options.export_dir, "InventorySticker")
|
||||
inventoryCSV = os.path.join(inventoryCSVParent, "inventory.csv")
|
||||
|
||||
|
||||
inventoryCSVParent = os.path.join(self.options.export_dir, "InventorySticker")
|
||||
inventoryCSV = os.path.join(inventoryCSVParent, "inventory.csv")
|
||||
|
||||
# To avoid messing with old stickers we remove the directory on Client before doing something new
|
||||
shutil.rmtree(inventoryCSVParent, ignore_errors=True) #remove the output directory before doing new job
|
||||
|
||||
# we are going to write the imported Server CSV file temporarily. Otherwise CSV reader seems to mess with the file if passed directly
|
||||
|
||||
# we are going to write the imported Server CSV file temporarily. Otherwise CSV reader seems to mess with the file if passed directly
|
||||
if not os.path.exists(inventoryCSVParent):
|
||||
os.mkdir(inventoryCSVParent)
|
||||
with open(inventoryCSV, 'w', encoding="utf-8") as f:
|
||||
f.write(inventoryData)
|
||||
f.close()
|
||||
|
||||
|
||||
#parse sticker Ids from user input
|
||||
if self.options.sticker_ids != "*":
|
||||
sticker_ids = self.options.sticker_ids.split(",")
|
||||
else:
|
||||
sticker_ids = None
|
||||
|
||||
|
||||
with open(inventoryCSV, 'r', encoding="utf-8") as csv_file:
|
||||
csv_reader = csv.reader(csv_file, delimiter=",")
|
||||
|
||||
|
||||
totalOutputs = 0
|
||||
for row in csv_reader:
|
||||
internal_id = row[0]
|
||||
@@ -454,7 +455,7 @@ class InventorySticker(inkex.Effect):
|
||||
sticker_id = row[2]
|
||||
level = row[3]
|
||||
zone = row[4]
|
||||
|
||||
|
||||
if sticker_ids is None or sticker_id in sticker_ids:
|
||||
totalOutputs += 1
|
||||
#create new sub directories for each non-existent FabLab zone (if flat export is disabled)
|
||||
@@ -467,23 +468,23 @@ class InventorySticker(inkex.Effect):
|
||||
os.mkdir(zoneDir)
|
||||
else:
|
||||
zoneDir = inventoryCSVParent #use top directory
|
||||
|
||||
|
||||
#Generate the recent sticker content
|
||||
stickerGroup = self.document.getroot().add(inkex.Group(id="InventorySticker_Id" + sticker_id)) #make a new group at root level
|
||||
DataMatrixStyle = inkex.Style({"stroke": "none", "stroke-width": "1", "fill": "#000000"})
|
||||
DataMatrixAttribs = {"style": str(DataMatrixStyle), "height": str(DataMatrix_xy) + "px", "width": str(DataMatrix_xy) + "px"}
|
||||
|
||||
|
||||
# 1 - create DataMatrix (create a 2d list corresponding to the 1"s and 0s of the DataMatrix)
|
||||
encoded = self.encode(self.options.target_url + "/" + sticker_id)
|
||||
DataMatrixGroup = stickerGroup.add(inkex.Group(id="DataMatrix_Id" + sticker_id)) #make a new group at root level
|
||||
for x, y in self.render_data_matrix(encoded, DataMatrix_xy):
|
||||
DataMatrixAttribs.update({"x": str(x + sticker_padding), "y": str(y + sticker_padding)})
|
||||
etree.SubElement(DataMatrixGroup, inkex.addNS("rect","svg"), DataMatrixAttribs)
|
||||
|
||||
inline_size = sticker_width - DataMatrix_width - 3 * sticker_padding #remaining width for objects next to the DataMatrix
|
||||
|
||||
inline_size = sticker_width - DataMatrix_width - 3 * sticker_padding #remaining width for objects next to the DataMatrix
|
||||
x_pos = DataMatrix_width + 2 * sticker_padding
|
||||
|
||||
# 2 - Add Object Name Text
|
||||
|
||||
# 2 - Add Object Name Text
|
||||
objectName = etree.SubElement(stickerGroup,
|
||||
inkex.addNS("text", "svg"),
|
||||
{
|
||||
@@ -491,18 +492,18 @@ class InventorySticker(inkex.Effect):
|
||||
"x": str(x_pos) + "px",
|
||||
#"xml:space": "preserve", #we cannot add this here because Inkscape throws an error
|
||||
"y": str(objectNameFontSize) + "px",
|
||||
"text-align" : "left",
|
||||
"text-anchor": "left",
|
||||
"text-align" : "left",
|
||||
"text-anchor": "left",
|
||||
"vertical-align" : "bottom",
|
||||
#style: inline-size required for text wrapping inside box; letter spacing is required to remove the additional whitespaces. The letter spacing depends to the selected font family (Miso)
|
||||
"style": str(inkex.Style({"fill": "#000000", "writing-mode": "horizontal-tb", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": globalFont, "font-weight": "bold", "letter-spacing": "-3.66px"}))
|
||||
"style": str(inkex.Style({"fill": "#000000", "writing-mode": "horizontal-tb", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": globalFont, "font-weight": "bold", "letter-spacing": "-3.66px"}))
|
||||
}
|
||||
)
|
||||
objectName.set("id", "objectName_Id" + sticker_id)
|
||||
objectName.set("xml:space", "preserve") #so we add it here instead .. if multiple whitespaces in text are coming after each other just render them (preserve!)
|
||||
objectNameTextSpan = etree.SubElement(objectName, inkex.addNS("tspan", "svg"), {})
|
||||
objectNameTextSpan.text = splitAt(doc_title, 1) #add 1 whitespace after each chacter. So we can simulate a in-word line break (break by char instead by word)
|
||||
|
||||
|
||||
# 3 - Add Object Id Text - use the same position but revert text anchors/align
|
||||
objectId = etree.SubElement(stickerGroup,
|
||||
inkex.addNS("text", "svg"),
|
||||
@@ -511,8 +512,8 @@ class InventorySticker(inkex.Effect):
|
||||
"x": str(sticker_padding) + "px",
|
||||
"y": "30px",
|
||||
"transform": "translate(0," + str(sticker_height - subline_fontsize) + ")",
|
||||
"text-align" : "left",
|
||||
"text-anchor": "left",
|
||||
"text-align" : "left",
|
||||
"text-anchor": "left",
|
||||
"vertical-align" : "bottom",
|
||||
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": globalFont, "font-weight": "bold"})) #inline-size required for text wrapping
|
||||
}
|
||||
@@ -520,7 +521,7 @@ class InventorySticker(inkex.Effect):
|
||||
objectId.set("id", "objectId_Id" + sticker_id)
|
||||
objectIdTextSpan = etree.SubElement(objectId, inkex.addNS("tspan", "svg"), {})
|
||||
objectIdTextSpan.text = "Thing #" + sticker_id
|
||||
|
||||
|
||||
# 4 - Add Owner Text
|
||||
owner = etree.SubElement(stickerGroup,
|
||||
inkex.addNS("text", "svg"),
|
||||
@@ -529,8 +530,8 @@ class InventorySticker(inkex.Effect):
|
||||
"x": str(x_pos) + "px",
|
||||
"y": "30px",
|
||||
"transform": "translate(0," + str(sticker_height - subline_fontsize) + ")",
|
||||
"text-align" : "right",
|
||||
"text-anchor": "right",
|
||||
"text-align" : "right",
|
||||
"text-anchor": "right",
|
||||
"vertical-align" : "bottom",
|
||||
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": globalFont, "font-weight": "300"})) #inline-size required for text wrapping
|
||||
}
|
||||
@@ -547,8 +548,8 @@ class InventorySticker(inkex.Effect):
|
||||
"x": str(x_pos) + "px",
|
||||
"y": "30px",
|
||||
"transform": "translate(0," + str(sticker_height - subline_fontsize - subline_fontsize) + ")",
|
||||
"text-align" : "right",
|
||||
"text-anchor": "right",
|
||||
"text-align" : "right",
|
||||
"text-anchor": "right",
|
||||
"vertical-align" : "bottom",
|
||||
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": globalFont, "font-weight": "bold"})) #inline-size required for text wrapping
|
||||
}
|
||||
@@ -556,7 +557,7 @@ class InventorySticker(inkex.Effect):
|
||||
levelText.set("id", "level_Id" + sticker_id)
|
||||
levelTextTextSpan = etree.SubElement(levelText, inkex.addNS("tspan", "svg"), {})
|
||||
levelTextTextSpan.text = level
|
||||
|
||||
|
||||
# 6 - Add horizontal divider line
|
||||
line_thickness = 2 #px
|
||||
line_x_pos = 350 #px; start of the line (left coord)
|
||||
@@ -569,24 +570,24 @@ class InventorySticker(inkex.Effect):
|
||||
}
|
||||
)
|
||||
divider.set("id", "divider_Id" + sticker_id)
|
||||
|
||||
|
||||
if self.options.preview == False:
|
||||
export_file_name = sticker_id + "_" + get_valid_filename(doc_title)
|
||||
export_file_path = os.path.join(zoneDir, export_file_name)
|
||||
|
||||
|
||||
#"Export" as SVG by just copying the recent SVG document to the target directory. We need to remove special characters to have valid file names on Windows/Linux
|
||||
export_file_svg = open(export_file_path + ".svg", "w", encoding="utf-8")
|
||||
export_file_svg.write(str(etree.tostring(self.document), "utf-8"))
|
||||
export_file_svg.close()
|
||||
|
||||
export_file_svg.close()
|
||||
|
||||
if self.options.export_png == False and self.options.export_svg == False:
|
||||
inkex.errormsg("Nothing to export. Generating preview only ...")
|
||||
break
|
||||
|
||||
|
||||
if self.options.export_png == True: #we need to generate SVG before to get PNG. But if user selected PNG only we need to remove SVG afterwards
|
||||
#Make PNG from SVG (slow because each file is picked up separately. Takes about 10 minutes for 600 files
|
||||
#Make PNG from SVG (slow because each file is picked up separately. Takes about 10 minutes for 600 files
|
||||
inkscape(export_file_path + ".svg", actions="export-dpi:96;export-background:white;export-filename:{file_name};export-do;FileClose".format(file_name=export_file_path + ".png"))
|
||||
|
||||
|
||||
#fix for "usb.core.USBError: [Errno 13] Access denied (insufficient permissions)"
|
||||
#echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2044", MODE="666"' > /etc/udev/rules.d/99-garmin.rules && sudo udevadm trigger
|
||||
if self.options.print_png > 0:
|
||||
@@ -594,7 +595,7 @@ class InventorySticker(inkex.Effect):
|
||||
inkex.errormsg("No file output for printing. Please set 'Export PNG' to true first.")
|
||||
else:
|
||||
for x in range(self.options.print_png):
|
||||
command = "brother_ql -m QL-720NW --backend pyusb --printer usb://" + self.options.print_device + " print -l 62 --600dpi -r auto " + export_file_path + ".png"
|
||||
command = self.options.brother_ql + " -m QL-720NW --backend pyusb --printer usb://" + self.options.print_device + " print -l 62 --600dpi -r auto " + export_file_path + ".png"
|
||||
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) #forr Windows: shell=False
|
||||
stdout, stderr = p.communicate()
|
||||
p.wait()
|
||||
@@ -608,11 +609,11 @@ class InventorySticker(inkex.Effect):
|
||||
|
||||
if self.options.export_svg != True: #If user selected PNG only we need to remove SVG again
|
||||
os.remove(export_file_path + ".svg")
|
||||
|
||||
|
||||
self.document.getroot().remove(stickerGroup) #remove the stickerGroup again
|
||||
else: #create preview by just breaking the for loop without executing remove(stickerGroup)
|
||||
break
|
||||
csv_file.close()
|
||||
csv_file.close()
|
||||
if totalOutputs == 0:
|
||||
self.msg("No output was generated. Check if your entered IDs are valid!")
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user