mirror of
https://github.com/inventree/inventree-website.git
synced 2025-04-28 05:26:44 +00:00
Merge pull request #196 from SergeoLacruz/main
Added multi printer docu für Zebra label printer plugin
This commit is contained in:
commit
cc2456bb5c
@ -5,8 +5,8 @@ license: MIT
|
||||
open_source: true # Is this project licensed with an OSI-approved license - aka 'open source'
|
||||
stable: true # Is this project stable? Should users deploy this in their instace?
|
||||
maintained: true # Is this project maintained?
|
||||
pypi: false # Is availanle via PyPi
|
||||
package_name: # Name of the package on the index, required if pypi true
|
||||
pypi: true # Is availanle via PyPi
|
||||
package_name: inventree-supplier-panel # Name of the package on the index, required if pypi true
|
||||
github: https://github.com/sergeolacruz/inventree-supplier-panel # Ĺink to repo in GitHub, one of github, gitlab or source is required
|
||||
issue_tracker: https://github.com/SergeoLacruz/inventree-supplier-panel/issues # Link to Issue tracker, optional
|
||||
categories: # Mixins/integrations that are used, optional
|
||||
@ -14,103 +14,257 @@ categories:
|
||||
tags: # Freetext tags - treat them like kewords, optional
|
||||
- supplier
|
||||
- mouser
|
||||
- digikey
|
||||
---
|
||||
Create Mouser shopping cart from purchase order
|
||||
# The InvenTree-supplier-panel
|
||||
|
||||
This is a plugin for [InvenTree](https://inventree.org), which translates a purchase order
|
||||
into a Mouser shopping cart. After using this plugin you can directly order the shopping
|
||||
cart on the Mouser WEB page. You need to have a Mouser account and a Mouser API key.
|
||||
The shopping cart will be created in your Mouser account.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
For this plugin to work you need to have Mouser as as supplier in your InvenTree data.
|
||||
Suppliers parts must be added to all the parts that you like to buy at Mouser. All Mouser supplier
|
||||
parts need to have the proper SKU. It needs to match the Mouser part number exactly.
|
||||
|
||||
For access to the Mouser API you need a Mouser account and a shopping cart API key.
|
||||
You can get this on the Mouser WEB page. Do not mess up with the Mouser search API
|
||||
key. This is different. If the key is properly set up you can find it on the Mouser
|
||||
WEB page here:
|
||||
|
||||

|
||||
This is a plugin for [InvenTree](https://inventree.org), which uploads a purchase order
|
||||
to a supplier WEB page. After using this plugin you can directly order the parts on
|
||||
supplier WEB page. You need to have a supplier account and a different kinds of API keys
|
||||
depending on the supplier.
|
||||
The data will be created in your supplier account. Each time you transfer your PO
|
||||
a new data set cart will be created. So make sure that you delete them from time to time in
|
||||
the supplier WEB interface.
|
||||
The plugin also helps to create supplierparts based on the supplier part number..
|
||||
Actually the plugin supports two suppliers: Mouser and Digikey.
|
||||
|
||||
## Installation
|
||||
The plugin is on pypi. You can install it by just calling:
|
||||
|
||||
```
|
||||
pip install git+https://github.com/SergeoLacruz/inventree-supplier-panel
|
||||
pip install inventree-supplier-panel
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Configuration
|
||||
|
||||
### Mouser Supplier ID
|
||||
Place here the primary key of the supplier Mouser in your system. You can select from a list of
|
||||
your suppliers.
|
||||
your suppliers. If this is not set the panel will not be displayed and a error is raised.
|
||||
|
||||
### Supplier API key
|
||||
Place here you Mouser key for manipulating shopping carts.
|
||||
### Digikey Supplier ID
|
||||
Place here the primary key of the supplier Digikey in your system. You can select from a list of
|
||||
your suppliers. If this is not set the panel will not be displayed and a error is raised.
|
||||
|
||||
### Supplier shopping cart key
|
||||
Each shopping cart on the Mouser page has a designated key. You can have several shopping carts
|
||||
in our account. Each cart has a separate key. The plugin puts your PO into the cart with this key.
|
||||
If you do not have a shopping cart key, leave the field empty. The plugin will create a cart
|
||||
and save the key in the field.
|
||||
### Mouser API key
|
||||
Place here your Mouser key for manipulating shopping carts. You find it in your Mouser account.
|
||||
|
||||
### Proxies
|
||||
### Digikey ID and Digikey Secret
|
||||
This is the client ID and the client secret that has been generated in the Digkey API admin WEB portal.
|
||||
Copy it from there to the InvenTree settings.
|
||||
|
||||
### Digikey token and Digikey refresh token
|
||||
These fields are filled automatically. The Digikey API requires two tokens with different life times.
|
||||
Please refer to the Digikey section for more information.
|
||||
|
||||
### Proxy CON
|
||||
Protocol to proxy server e.g. https
|
||||
|
||||
### Proxy URL
|
||||
In case you need to authorise a proxy server between your InvenTree server and the internet
|
||||
put the required setting here. The argument for the request is {'Proxy CON' : 'Proxy URL'} for
|
||||
example:
|
||||
put the required setting here. Example:
|
||||
|
||||
```
|
||||
{ 'https' : 'https://user:password@ipaddress:port' }
|
||||
https://user:password@ipaddress:port
|
||||
```
|
||||
|
||||
If you do not need this just leave Proxy CON empty.
|
||||
If you do not need this just leave the fields empty.
|
||||
A proxy can also be set using the environment variables PROXY_CON and PROXY_URL. The
|
||||
values in the environment variables overwrite InvenTree settings.
|
||||
|
||||
## What it does
|
||||
### Base URL
|
||||
The base URL for server instance is in the Server Settings category of InvenTree. The plugin
|
||||
uses this setting to build the OAuth callback for Digikey. Put the correct URL here.
|
||||
|
||||
The plugin creates a new panel which is visible on the purchase order details view.
|
||||
This is called Mouser actions. On the panel there are three things:
|
||||
## What the plugin does
|
||||
|
||||
- a button that starts the transfer of your PO to Mouser
|
||||
The plugin creates a new panel which is visible on the purchase order details view.
|
||||
This is called either Mouser actions or Digikey actions depending on the supplier of the
|
||||
active PO. On the panel there are three things:
|
||||
|
||||
- a button that starts the transfer of your PO to the supplier
|
||||
- a status bar that shows error messages
|
||||
- a table that contains the created Mouser shopping cart.
|
||||
- a table that contains the created Mouser shopping cart.
|
||||
- in case of Digikey a button that initiates the token generation.
|
||||
|
||||

|
||||
|
||||
The button initiates the transfer. It takes each element of your PO, takes the SKU of
|
||||
the Mouser supplier part and adds it into your shopping cart. When finished it downloads
|
||||
the shopping cart from the Mouser WEB page and puts the data into the table. Here you see
|
||||
the actual stock at mouser and an OK bubble when the stock is large enough for you order.
|
||||
You also find the actual price as well as the total amount of your order.
|
||||
The button "Transfer PO" initiates the transfer. It takes each element of your PO using the SKU of
|
||||
the supplier part and transfers it to the suppliers WEB shop. When finished it downloads
|
||||
the data from the WEB page and puts the data into the table. Here you see
|
||||
the actual stock at the supplier and an OK bubble when the stock is large enough for you order.
|
||||
You also find the actual price as well as the total amount of your order. If the supplier
|
||||
detects an error with the part it is displayed in the very right column.
|
||||
|
||||
All items that have been in the cart before get deleted. The cart always contains only the parts
|
||||
in your PO. SergelLacruz
|
||||
|
||||
The plugin also transfers your IPNs (internal part numbers). Mouser reserves a field
|
||||
The plugin also transfers your IPNs (internal part numbers). Most suppliers reserve a field
|
||||
for such numbers. They show up in your shopping cart as well as on the invoice and even
|
||||
on the labels that they put onto the bags and reels.
|
||||
on the labels that they put onto the bags and reels.
|
||||
|
||||
Finally the prices that come with the Mouser shopping cart will be copied back into your
|
||||
Finally the actual prices are copied back into your
|
||||
InvenTree purchase order line items. So you can always see what you payed for the part when
|
||||
you ordered it. This does not modify the price breaks of the supplier part. These are stored
|
||||
with the supplier part. Here we just modify the purchase order.
|
||||
with the supplier part. Here we just modify the purchase order.
|
||||
|
||||
The panel is only displayed when the supplier of the current purchase order is Mouser.
|
||||
In addition the current user must have change, add or delete access to purchase orders.
|
||||
## Working with Mouser
|
||||
|
||||
### Set up
|
||||
|
||||
For this plugin to work you need to have Mouser as a supplier in your InvenTree database.
|
||||
Supplierparts must be added to all the parts that you like to buy at Mouser. All Mouser supplier
|
||||
parts need to have the proper SKU. It needs to match the Mouser part number exactly.
|
||||
|
||||
For access to the Mouser API you need a Mouser account and a shopping cart API key.
|
||||
You can get this in your Mouser WEB account. Do not mess up with the Mouser search API
|
||||
key. This is a different one. If the key is properly set up you can find it on the Mouser
|
||||
WEB page here:
|
||||

|
||||
|
||||
### Usage
|
||||
Using Mouser is easy. Only the Mouser shopping cart key is required for authentication. Its lifetime
|
||||
is endless. Mouser has an API for the shopping cart. On pressing the button a shopping
|
||||
cart is crated and all items are put into this shopping cart. When you login to the
|
||||
Mouser WEB shop you can use this shopping cart for your order.
|
||||
|
||||
Please be aware that the plugin creates a new cart with a new ID each time the button is pressed.
|
||||
If you afterwards create a order in the WEB UI, be careful selecting the right one
|
||||
and delete all unused carts.
|
||||
|
||||
#### Currency support
|
||||
Mouser needs a country code for currency support. The plugin selects a proper country based on
|
||||
the InvenTree currency setting and transfers this to Mouser. Mouser sends back the sopping cart
|
||||
in the correct currency. The currency name is shown in last line of the table.
|
||||
|
||||
## Working with Digikey
|
||||
|
||||
### Set up
|
||||
|
||||
You need a registration on the [Digikey API products WEB page](https://developer.digikey.com).
|
||||
This is not your normal Digikey account for shopping. You have to apply separately. After
|
||||
registration create an organisation and inside the organization a production app.
|
||||
The most important thing to set is the OAuth Callback. This is an URL on your local server
|
||||
that is called by Digikey for key generation. The plugin sets up an URL for this.
|
||||
Just add your local IP. The entry should look somehow like:
|
||||
|
||||
```
|
||||
https://192.168.1.40:8123/plugin/suppliercart/digikeytoken/
|
||||
```
|
||||
|
||||
In this example 192.168.1.40:8123 is the local IP address and port where my
|
||||
InvenTree development server runs. Place here the appropriate address.
|
||||
In Production products section make sure that Product information and MyLists is activated.
|
||||
|
||||
In the View tab of your app you find the Client-ID and the Client-Secret. Place those in
|
||||
the plugin settings.
|
||||
|
||||
Digikey Supplierparts have to by in your InvenTree Database as described already in
|
||||
the Mouser section.
|
||||
|
||||
### Usage
|
||||
Using Digikey is more complex. The authorisation system is token based and they do not
|
||||
have a shopping cart API.
|
||||
|
||||
#### Authorization
|
||||
The Digikey Client ID and the Client secret are the first things you need. With those
|
||||
you call an API endpoint. You HAVE to go through an interactive browser window and
|
||||
enter your credentials. Afterwards Digikey opens a callback URL on your local machine
|
||||
and transfers a key. With this key the plugin calls another API endpoint to create
|
||||
a token and a refresh token. The key gets bad after 60 seconds.
|
||||
|
||||
The token is used for each call to a Digikey API. It is good for 30 minutes. It has to
|
||||
be refreshed using the refresh token. This one is valid for 90 days.
|
||||
|
||||
The plugin has a button in the panel that initiates the first step. It opens a browser
|
||||
where you enter your credentials. When the OAuth callback is properly set the URL
|
||||
...plugin/suppliercart/digikeytoken/ is called. This triggers a call to
|
||||
https://api.digikey.com/v1/oauth2/token from where the plugin get the tokens. The tokens
|
||||
are stored in the plugin setting area. Do not change them manually.
|
||||
|
||||
Each time you transfer a PO the refresh token is called independently from the
|
||||
tokens live time. This also refreshes the refresh token. So you are save when
|
||||
you use the plugin ate least once in 90 days. In case the token gets bad you need to
|
||||
create a fresh set using the token button again.
|
||||
|
||||
If you are confused now read the documentation on the Digikey WEB page for more details.
|
||||
|
||||
#### MyLists
|
||||
Digikey does not have such a simple shopping cart API. The plugin uses the MyLists API.
|
||||
It creates a list on the WEB shop that can easily be transferred to a shopping
|
||||
cart. When creating a list a list name has to be provided. The plugin creates a name
|
||||
based on the PO name and adding a -xx that counts upwards each time you push the button.
|
||||
The reason is that each name is allowed only once. Even when the list is deleted, the
|
||||
name stays blocked forever. If you are done with your order delete the lists from your
|
||||
Digikey WEB account.
|
||||
|
||||
#### Currency support
|
||||
Digikey requires a country code and a currency code. The plugin uses the same translation
|
||||
as mentioned in the Mouser section and transfers both to Digikey. Digikey sends back the
|
||||
list in the correct currency. Unfortunately the currency code is not sent back. The only
|
||||
thing Digikey sends is a currency symbol but no info if $ is USD, AUD or whatever kind of Dollar.
|
||||
The plugin shows the symbol in the table for control.
|
||||
|
||||
## Automatically add supplierparts
|
||||
The plugin can add supplierparts based on the supplier part number. For users with
|
||||
edit part permission a panel called "Automatic Supplier parts" is shown. Here
|
||||
you can select the supplier and add the exact supplier part number. The plugin
|
||||
will create a corresponding supplierpart. I can fill the following part fields automatically:
|
||||
|
||||
- Supplier part number
|
||||
- URL
|
||||
- Package when available
|
||||
- Lifecycle status
|
||||
- Minimum order
|
||||
- Description
|
||||
|
||||
If the supplier does not provide information for a field it it left empty.
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
def get_custom_panels(self, view, request)
|
||||
```
|
||||
|
||||
This defines the panel. The function must return a panels list. Here it returns just one
|
||||
panel. The panel is returned under three conditions: The view must be PurchaseOrderDetail,
|
||||
the supplier must be Mouser or Digikey and the user must have edit permissions to purchase orders.
|
||||
The content_template is an html file that defines how the panel content looks.
|
||||
|
||||
```
|
||||
re_path(r'transfercart/(?P<pk>\d+)/', self.TransferCart, name='transfer-cart'),
|
||||
```
|
||||
Here we define the url that controls the panel. Let's look at the details here:
|
||||
|
||||
- ```name='transfer-cart'```: This is the name under which the url is called from the html file. We will
|
||||
come to that later when we discuss the template.
|
||||
|
||||
- ```self.TransferCart``` is the function that is called. It is defined later in this plugin
|
||||
|
||||
- ```transfercart/(?P<pk>\d+)/``` The string that looks a bit like white noise defines the url. transfercart
|
||||
is the url which can be chosen freely. The ? is well known for parameters. In this case we get just one
|
||||
parameter, the orders primary key. \d+ is a regular expression that limits the parameters to a digital
|
||||
number with n digits.
|
||||
|
||||
May be it is worth to leave a few more words on this. We define the url of the plugin. This is called by the Javascript
|
||||
function when we push the button. Let's have a look on the names and how they belong together:
|
||||
|
||||

|
||||
|
||||
In the picture you see the relevant lines in the python and java code. The names in the coloured boxes need to match.
|
||||
In case something does not fit the panel will not render and you will get an error message.
|
||||
|
||||
## Issues
|
||||
### Mouser messed up
|
||||
It can happen that the Mouser shopping cart API gets messed up and no item are added into
|
||||
your cart. Just delete the cart in that case and delete the key in the plugin setting.
|
||||
A new key will be created and usually works.
|
||||
|
||||
### API keys are global
|
||||
The API keys and especially the proxy password are user specific and shall not be given to
|
||||
The API keys and especially the proxy password are user specific and shall not be given to
|
||||
others. Up to now there are no user specific settings in InvenTree. So these keys are global
|
||||
and visible to, at least every admin. All users who use the plugin will have the same
|
||||
keys. We use a team key to solve this.
|
||||
|
||||
### Other suppliers
|
||||
Actually this works only for Mouser. Other suppliers like Digikey, Farnell or Buerklin
|
||||
might follow.
|
||||
### Missing DigiKey features
|
||||
Digikey allows more features like customer ID and list owners. These are not implemented so far.
|
||||
The plugin supports just a single Digikey organization and user. Some APIs require a createdBy
|
||||
value to be set. xxxx works fine so far.
|
||||
|
||||
### https Callback
|
||||
The OAuto callback setting in your Digikey WEB account allows only https. http is not allowed.
|
||||
This is usually not a problem in production environments. However the development server
|
||||
usually runs http. But InvenTree has the required stuff for https on board. I just changed
|
||||
the runserver to runsslserver in tasks.py.
|
||||
|
@ -17,14 +17,16 @@ tags: Label Printer Zebra ZPL
|
||||
---
|
||||
Zebra Label Printer Plugin for Inventree
|
||||
|
||||
This is a label printing plugin for [InvenTree](https://inventree.org), which provides support for Zebra Label printers .
|
||||
It was only tested with GK420T but should work for other ZPL printers too. It uses the ZPL library to
|
||||
convert the png data provided by InvenTree to Zebra's bitmap format.
|
||||
This is a label printing plugin for [InvenTree](https://inventree.org), which provides
|
||||
support for Zebra Label printers. It was only tested with GK420T but should work for
|
||||
other ZPL printers too. It uses the ZPL library to convert the png data provided by
|
||||
InvenTree to Zebra's bitmap format.
|
||||
|
||||
It can output the print data either to a local printer connected to the computer via USB or to a network printer
|
||||
with an IP address. The output can be configured in the InvenTree plugin user interface.
|
||||
It can output the print data either to a local printer connected to the computer via
|
||||
USB or to a network printer with an IP address. The output can be configured in the
|
||||
InvenTree plugin user interface.
|
||||
|
||||
Error handling is very basic.
|
||||
Error handling is very basic.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -33,7 +35,7 @@ The plugin is on pypi. Install this plugin using pip with the following command:
|
||||
```
|
||||
pip install inventree-zebra-plugin
|
||||
```
|
||||
|
||||
|
||||
## Configuration Options
|
||||
### Printer Interface
|
||||
Here you can chose between Local printer or network printer. Default value is a local printer.
|
||||
@ -41,27 +43,27 @@ Here you can chose between Local printer or network printer. Default value is a
|
||||
### IP address
|
||||
In case you use an IP printer set the IPv4 address here.
|
||||
|
||||
### Port
|
||||
### Port
|
||||
In case you use an IP printer set the port number here. The default port number is 9100.
|
||||
|
||||
### Local Device
|
||||
In case of a local printer set the device here. The plugin actually puts the data directly to the
|
||||
device /dev/usb/lp0. No printer spooler is involved so far.
|
||||
device /dev/usb/lp0. No printer spooler is involved so far.
|
||||
|
||||
### Threshold
|
||||
The image from pillow comes in greyscale. The plugin converts it ti pure BW because this gives a much
|
||||
### Threshold
|
||||
The image from pillow comes in greyscale. The plugin converts it ti pure BW because this gives a much
|
||||
better print result. The threshold between black and white can be adjusted here.
|
||||
|
||||
### Darkness
|
||||
### Darkness
|
||||
This is a value that influences the darkness of the print. Allowed values are 0 (white) to 30 (black).
|
||||
It is directly converted to a SD command in ZPL. If your black areas tend to blur out reduce the
|
||||
It is directly converted to a SD command in ZPL. If your black areas tend to blur out reduce the
|
||||
darkness.
|
||||
|
||||
### Dots per mm
|
||||
### Dots per mm
|
||||
This sets the resolution of the printer. You can choose between 8, 12 and 24
|
||||
dpmm depending on your printer model.
|
||||
|
||||
### Printer init
|
||||
### Printer init
|
||||
This string added to the printer output. It can be used to set special commands
|
||||
e.g. label rotation, mirror or white on black. Please refer to the ZPL manual
|
||||
for more information.
|
||||
@ -74,8 +76,8 @@ is passed directly to the printer without any checks. So be careful when editing
|
||||
here.
|
||||
|
||||
## Label Template
|
||||
The label needs a template described in html and css. The template should start with a page definition
|
||||
that defines the label size as shown below:
|
||||
The label needs a template described in html and css. The template should
|
||||
start with a page definition that defines the label size as shown below:
|
||||
|
||||
```
|
||||
@page {
|
||||
@ -89,13 +91,32 @@ that defines the label size as shown below:
|
||||
}
|
||||
```
|
||||
|
||||
The height and width parameters are defined in the InvenTree admin panel in the label section. These values
|
||||
have to fit the label size that is in the printer. See the example templates for details on template definition.
|
||||
The height and width parameters are defined in the InvenTree admin panel
|
||||
in the label section. These values have to fit the label size that is in
|
||||
the printer. See the example templates for details on template definition.
|
||||
|
||||
## Multi printer hack
|
||||
We have the requirement to print labels in different sizes. As we do not
|
||||
want to change the reel for each print we set up a second printer loaded
|
||||
with a different label size. InvenTree is not yet able to handle different
|
||||
printers. So I added a multi printer hack. You can define a key with an IP
|
||||
address in the label meta data:
|
||||
|
||||
```
|
||||
{"ip_address":"xxx.yyy.zzz.eee"}
|
||||
{"darkness":xx}
|
||||
```
|
||||
|
||||
If the printer driver finds that key, the IP address from the printer settings
|
||||
is overwritten with the address from the meta data. So the print will end up
|
||||
in another printer.
|
||||
|
||||
Only the IP address and darkness can be overwritten so far. All other settings remain.
|
||||
|
||||
## How it works
|
||||
First import all the stuff you need. Here we use the translation mechanism from Django for multi language support.
|
||||
The import the InvenTree libs and everything you need for plugin. Here we have ZPL for the Zebra bitmaps and socket
|
||||
for the IP connection to the printer.
|
||||
for the IP connection to the printer.
|
||||
|
||||
The next part is this:
|
||||
|
||||
@ -111,7 +132,7 @@ class ZebraLabelPlugin(LabelPrintingMixin, SettingsMixin, IntegrationPluginBase)
|
||||
```
|
||||
|
||||
The name of the class can be freely chosen. You reference to it in the entry_points section of the setup.py file.
|
||||
The parameters need to be like in the example. Then there is the description block. The keywords are fixed and
|
||||
The parameters need to be like in the example. Then there is the description block. The keywords are fixed and
|
||||
need to be like that. The values are found in the UI as shown in the pictures below.
|
||||
|
||||

|
||||
@ -137,9 +158,9 @@ SETTINGS = {
|
||||
```
|
||||
|
||||
We need to define a dict with the name SETTINGS. Please be aware the keys need to be in all CAPITAL letters like CONNECTION.
|
||||
Simple parameters are just text strings like the port. We can set a default. The name and description shows up in the UI.
|
||||
Simple parameters are just text strings like the port. We can set a default. The name and description shows up in the UI.
|
||||
Instead of a simple text we can also use choices. The first string like "local" it the key you use in the code. The second
|
||||
one is the description in the UI.
|
||||
one is the description in the UI.
|
||||
After that we need to define a function:
|
||||
|
||||
```python
|
||||
@ -157,37 +178,37 @@ The kwargs is a dict with the following keys:
|
||||
- height
|
||||
- png_file
|
||||
|
||||
The item_instance is the part to be printed. This allows direct access to all part data. The arguments width and height
|
||||
come from the settings of the label in the admin interface. NOT from the html template.
|
||||
For the Zebra printer we use the png_file. This is a PIL (python Pillow) object with the graphic of the label in PNG format.
|
||||
The PIL object is a greyscale image. Because the printer can just print pure BW we convert this to a BW picture.
|
||||
The item_instance is the part to be printed. This allows direct access to all part data. The arguments width and height
|
||||
come from the settings of the label in the admin interface. NOT from the html template.
|
||||
For the Zebra printer we use the png_file. This is a PIL (python Pillow) object with the graphic of the label in PNG format.
|
||||
The PIL object is a greyscale image. Because the printer can just print pure BW we convert this to a BW picture.
|
||||
|
||||
```python
|
||||
fn = lambda x : 255 if x > Threshold else 0
|
||||
label_image = label_image.convert('L').point(fn, mode='1')
|
||||
```
|
||||
|
||||
The threshold can by modified by a plugin parameter. 200 is a good starting value. This trick gives much better prints.
|
||||
We can put the result of this directly into the ZPL library.
|
||||
The threshold can by modified by a plugin parameter. 200 is a good starting value. This trick gives much better prints.
|
||||
We can put the result of this directly into the ZPL library.
|
||||
|
||||
```python
|
||||
l = zpl.Label(Height, Width, dpmm)
|
||||
l.origin(0, 0)
|
||||
li.set_darkness(darkness)
|
||||
...
|
||||
l.write_graphic(label_image, Width)
|
||||
l.endorigin()
|
||||
```
|
||||
|
||||
Width and Height define is the size of the label in millimeters as described above.
|
||||
The third parameter is the resolution of the printer in dots per mm.
|
||||
write_graphic converts the pillow data to ZPL.
|
||||
The third parameter is the resolution of the printer in dots per mm.
|
||||
write_graphic converts the pillow data to ZPL.
|
||||
|
||||
The plugin was tested with a labels of various sizes defined using css and html. The DPI scaling
|
||||
can be chosen in the InvenTree settings. 800 is a good value because it gives high quality.
|
||||
|
||||
The rest of the code is just output to the printer on different interfaces.
|
||||
|
||||
## Quality matters
|
||||
## Quality matters
|
||||
The InvenTree printer system uses a graphical representation of the label. The label is described
|
||||
in HTML, converted to a pixel graphic and printed. The advantage is independency from printer
|
||||
models and systems. Disadvantage is larger data and quality problems with darkness and scaling.
|
||||
@ -195,22 +216,22 @@ Let's have a look at the following printout:
|
||||
|
||||

|
||||
|
||||
Both codes have been printed with the same printer on the same reel. The left one is
|
||||
hardly readable using my mobile. The right one reads easily even as it is smaller.
|
||||
Both codes have been printed with the same printer on the same reel. The left one is
|
||||
hardly readable using my mobile. The right one reads easily even as it is smaller.
|
||||
|
||||
### Secret 1, Scale
|
||||
The printer resolution is 8 dots per mm resulting in a dot size of 0.125mm. The QR code pixel
|
||||
The printer resolution is 8 dots per mm resulting in a dot size of 0.125mm. The QR code pixel
|
||||
and the printer pixel size should be integrally divisible. The code in the picture has 21
|
||||
pixels plus one in the frame, so 23 pixel. The frame is set in the HTML description.
|
||||
pixels plus one in the frame, so 23 pixel. The frame is set in the HTML description.
|
||||
|
||||
```
|
||||
{% raw %}{% qrcode qr_data border=1 %}{% endraw %}
|
||||
```
|
||||
|
||||
I selected two dots per pixel. So 23 * 2 * 0.125 = 6.125mm. If the size is something different
|
||||
scaling takes place and the result might be worse. If you like a larger printout select more
|
||||
scaling takes place and the result might be worse. If you like a larger printout select more
|
||||
dots per pixel. From a certain size upwards the value does not matter any more because the code
|
||||
gets large enough to be readable in any quality.
|
||||
gets large enough to be readable in any quality.
|
||||
|
||||
### Secret 2: Darkness
|
||||
Zebra printers allow to set the darkness of the print in values between 0 (white) and 30 (max)
|
||||
@ -218,10 +239,10 @@ The left code was printed with a value 0r 30. The black dots tend to blur out a
|
||||
in smaller white areas. The right code was printed with a value of 25 resulting in larger white
|
||||
pixels. The darkness values are just examples. Your values will differ based on printer model,
|
||||
media type and printer age. The printer head tends to wear out and the darkness value might
|
||||
need an adjustment from time to time.
|
||||
need an adjustment from time to time.
|
||||
|
||||
### Alternative
|
||||
You can also bypass the InvenTree template and printing system and directly create ZPL from
|
||||
You can also bypass the InvenTree template and printing system and directly create ZPL from
|
||||
the parts data. The printer knows best how to render the label and the print quality is best.
|
||||
If you are interested in this way have a look at the [inventree-zpl-plugin](https://github.com/yellowcrescent/inventree-zpl-plugin)
|
||||
that does exactly that.
|
||||
If you are interested in this way have a look at the [inventree-zpl-plugin](https://github.com/yellowcrescent/inventree-zpl-plugin)
|
||||
that does exactly that.
|
||||
|
330
assets/plugins/plugin_dataflow.svg
Normal file
330
assets/plugins/plugin_dataflow.svg
Normal file
@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="645.68176"
|
||||
height="364.82404"
|
||||
viewBox="0 0 170.83664 96.52636"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
sodipodi:docname="plugin_dataflow.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs2">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker5167"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path5165" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="marker4285"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
id="path4283"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker4237"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path4235" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="marker1903"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
id="path1901"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker1867"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
transform="matrix(-0.8,0,0,-0.8,-10,0)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path1865" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="366.68537"
|
||||
inkscape:cy="162.63456"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-55.356916,-137.03246)">
|
||||
<rect
|
||||
style="opacity:1;fill:#00fc00;fill-opacity:0.271186;stroke:none;stroke-width:0.386441;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect6345"
|
||||
width="29.621031"
|
||||
height="6.430881"
|
||||
x="83.372101"
|
||||
y="147.95529" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
x="68.770256"
|
||||
y="183.03281"
|
||||
id="text817"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan815"
|
||||
x="68.770256"
|
||||
y="183.03281"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833;font-size:4.23333333px">def TransferCart(self,request,pk):</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="68.770256"
|
||||
y="188.17725"
|
||||
id="tspan819"
|
||||
style="stroke-width:0.385833;-inkscape-font-specification:Arial;font-family:Arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:4.23333333px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
x="68.803177"
|
||||
y="167.63768"
|
||||
id="text823"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan821"
|
||||
x="68.803177"
|
||||
y="167.63768"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833;font-size:4.23333333px">re_path(r'transfercart/(?P<pk>\d+)/', self.TransferCart, name='transfer-cart'),</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="68.803177"
|
||||
y="172.78212"
|
||||
id="tspan825"
|
||||
style="stroke-width:0.385833;-inkscape-font-specification:Arial;font-family:Arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:4.23333333px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
x="68.95134"
|
||||
y="215.77185"
|
||||
id="text829"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan827"
|
||||
x="68.95134"
|
||||
y="215.77185"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833;font-size:4.23333333px">response = await fetch( "{% url 'plugin:suppliercart:transfer-cart' order.pk %}");</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="68.95134"
|
||||
y="220.91629"
|
||||
id="tspan831"
|
||||
style="stroke-width:0.385833;-inkscape-font-specification:Arial;font-family:Arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:4.23333333px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
x="68.95134"
|
||||
y="152.43741"
|
||||
id="text835"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan833"
|
||||
x="68.95134"
|
||||
y="152.43741"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833;font-size:4.23333333px">SLUG = "suppliercart"</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="68.95134"
|
||||
y="157.58185"
|
||||
id="tspan837"
|
||||
style="stroke-width:0.385833;-inkscape-font-specification:Arial;font-family:Arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:4.23333333px" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot839"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.6667px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
id="flowRegion841"><rect
|
||||
id="rect843"
|
||||
width="628.81995"
|
||||
height="259.60919"
|
||||
x="226.77925"
|
||||
y="227.5103" /></flowRegion><flowPara
|
||||
id="flowPara845" /></flowRoot>
|
||||
<text
|
||||
id="text851"
|
||||
y="140.16028"
|
||||
x="68.95134"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833"
|
||||
y="140.16028"
|
||||
x="68.95134"
|
||||
id="tspan847"
|
||||
sodipodi:role="line"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Arial;-inkscape-font-specification:'Arial Bold'"
|
||||
id="tspan508">Python</tspan>:</tspan><tspan
|
||||
id="tspan849"
|
||||
y="145.45195"
|
||||
x="68.95134"
|
||||
sodipodi:role="line"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;font-family:Arial;-inkscape-font-specification:Arial;stroke-width:0.385833" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.385833"
|
||||
x="68.95134"
|
||||
y="207.00249"
|
||||
id="text857"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan853"
|
||||
x="68.95134"
|
||||
y="207.00249"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Arial;-inkscape-font-specification:'Arial Bold';stroke-width:0.385833;font-size:4.23333333px">Javascript:</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="68.95134"
|
||||
y="212.14693"
|
||||
id="tspan855"
|
||||
style="stroke-width:0.385833;-inkscape-font-specification:'Arial Bold';font-family:Arial;font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal;font-size:4.23333333px" /></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.385833px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1867)"
|
||||
d="m 200.68696,170.75568 -21.82601,39.3648"
|
||||
id="path1857"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.385833px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1903)"
|
||||
d="m 205.94859,213.8231 c 0,0 25.18708,-30.34967 19.09778,-46.96492 -6.08931,-16.61525 -74.12998,-12.01197 -81.6527,-11.69252 -7.52274,0.31945 -13.64127,8.37964 -13.64127,8.37964"
|
||||
id="path1893"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="czzc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.385833px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4237)"
|
||||
d="m 128.19339,169.78131 -1.75387,8.96427"
|
||||
id="path4227"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.385833px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4285)"
|
||||
d="m 159.17856,169.39156 c 0,0 -0.75265,17.80781 -13.25151,27.08765 -12.49887,9.27983 -28.52853,8.82481 -37.61092,-0.19487 -9.082401,-9.01969 -11.49764,-11.30276 -11.49764,-11.30276"
|
||||
id="path4275"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="czzc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.385833px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5167)"
|
||||
d="m 95.259485,154.38618 c -9.916792,8.89186 -32.64802,-0.23951 -36.44166,11.6925 -3.79364,11.93202 -6.518614,38.04326 5.066754,54.75993 11.585367,16.71667 49.333961,12.29554 62.944691,11.10787 13.61074,-1.18767 27.08767,-13.44637 27.08767,-13.44637"
|
||||
id="path5157"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="czzzc" />
|
||||
<rect
|
||||
y="211.09485"
|
||||
x="140.51495"
|
||||
height="6.430881"
|
||||
width="22.995275"
|
||||
id="rect6347"
|
||||
style="opacity:1;fill:#00fc00;fill-opacity:0.271186;stroke:none;stroke-width:0.340488;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="opacity:1;fill:#00fc9d;fill-opacity:0.271186;stroke:none;stroke-width:0.325615;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect6349"
|
||||
width="22.967285"
|
||||
height="6.430881"
|
||||
x="184.13921"
|
||||
y="162.93837" />
|
||||
<rect
|
||||
y="211.14491"
|
||||
x="188.47086"
|
||||
height="6.430881"
|
||||
width="16.564394"
|
||||
id="rect6351"
|
||||
style="opacity:1;fill:#00fc9d;fill-opacity:0.271186;stroke:none;stroke-width:0.276527;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
y="162.96066"
|
||||
x="125.46516"
|
||||
height="6.430881"
|
||||
width="5.6513834"
|
||||
id="rect6353"
|
||||
style="opacity:1;fill:#001d9d;fill-opacity:0.271186;stroke:none;stroke-width:0.386441;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="opacity:1;fill:#001d9d;fill-opacity:0.271186;stroke:none;stroke-width:0.386441;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect6355"
|
||||
width="5.6513834"
|
||||
height="6.430881"
|
||||
x="124.49078"
|
||||
y="178.55069" />
|
||||
<rect
|
||||
y="163.23907"
|
||||
x="144.82449"
|
||||
height="6.430881"
|
||||
width="24.164528"
|
||||
id="rect6357"
|
||||
style="opacity:1;fill:#e7fc9d;fill-opacity:0.271186;stroke:none;stroke-width:0.345643;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="opacity:1;fill:#e7fc9d;fill-opacity:0.271186;stroke:none;stroke-width:0.348694;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect6359"
|
||||
width="24.592999"
|
||||
height="6.430881"
|
||||
x="75.181656"
|
||||
y="178.07739" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
Loading…
x
Reference in New Issue
Block a user