-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Device dependent margins/config #54
Device dependent margins/config #54
Conversation
Each printer/labeler now has a config class instance setting its specific properties.
@@ -57,7 +57,7 @@ def _init_elements(self) -> None: | |||
|
|||
def update_labeler_context( | |||
self, | |||
supported_tape_sizes: tuple[int, ...], | |||
supported_tape_sizes: list[int], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really needed? In case the list is constant, it would be better as a convention to prefer tuple over list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The amount of supported tape sizes varies from printer to printer. I got an error that I tried to change the "tuple" size. A quick google search gave this as a solution. If there is a better solution please elaborate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, tuples are "immutable" so you should always generate a new one instead of modifying an old one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed (It didn´t complain this time)
Edit:
Sorry, had to revert this:
src/labelle/gui/gui.py:66: error: Argument "supported_tape_sizes" to "update_labeler_context" of "QSettingsToolbar" has incompatible type "list[int]"; expected "tuple[int, ...]" [arg-type]
Found 1 error in 1 file (checked 46 source files)
|
||
# Default printer margins (for each tape size) | ||
# { tapeSizeMm : (firstVisiblePixel, lastVisiblePixel) } | ||
labeler_label_vertical_margins: dict[int, Any] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like:
labeler_label_vertical_margins: dict[int, tuple[int, int]]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering what "first" and "last" mean here regarding visible pixels. I don't know offhand which pixel ordering is being used, so it would be helpful to be more explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is outdated btw
# Default printer margins (for each tape size) | ||
# { tapeSizeMm : (firstVisiblePixel, lastVisiblePixel) } | ||
labeler_label_vertical_margins: dict[int, Any] | ||
|
||
def __init__( | ||
self, | ||
tape_size_mm: int | None = None, | ||
device: UsbDevice | None = None, | ||
): | ||
if tape_size_mm is None: | ||
tape_size_mm = self.DEFAULT_TAPE_SIZE_MM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still relevant? Is DEFAULT_TAPE_SIZE_MM
supported by all printer models? I guess we should choose the first available tape size for the model instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really happy about this either, but the whole software kind of relies on it (also with the GUI)
Maybe I should convert it to take the smallest or largest supported tape size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it, it now takes the highest supported size if not set.
Maybe this would make a good "user preference" in the future.
src/labelle/lib/constants.py
Outdated
SUPPORTED_PRODUCTS = { | ||
SUPPORTED_DEVICE_ID.LABELMANAGER_PC: | ||
# ---- Supported USB Devices configuration ---- | ||
SUPPORTED_PRODUCTS.append( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that eventually we should move these out of the code, into a JSON file. If possible, please try to do it. In not, we can try refactor it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a proper data model(1) for device models(2). (The first "model" in that sentence means "implementation of a schema" while the second "model" refers to a particular product that is sold by a company.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was a bit confused You do already define DeviceConfig
.
I'm wondering how to achieve this elegantly. I agree that it's in principle better to define these things out-of-code like in JSON. However, that unfortunately would require deserialization. Pydantic is a popular library for this, but then we're adding more project dependencies and a lot of complexity, also in the code.
The way I see it, we have two main options:
- Separate the device definition from code, perform deserialization, probably add a dependency on something like Pydantic.
- Keep the device definitions as code, but switch to
NamedTuple
ordataclass
.
Another disadvantage to 1. is that editing the config would involve editing serialized data which can be a real pain. An advantage to 2. is that code tools like mypy will automatically perform validation for the data-as-code.
I'm leaning more towards 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I was trying to achieve here is a what in C would be "array of structs" for the "const" configuration.
If you have any suggestions how to do this elegantly in python, let me know...
My idea was that the (existing) labeler class took this as input to know the device's exact properties.
I agree that having this out of code would be nice. But can we please keep this contained?
I started out with "just adding my printer", and it is getting a bit out of hand already imho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted DeviceConfig to a dataclass
src/labelle/lib/constants.py
Outdated
SUPPORTED_DEVICE_ID.LABELPOINT_350: | ||
[int(SUPPORTED_DEVICE_ID.LABELMANAGER_PC)], | ||
# ToDo: Validate config! | ||
128, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to understand from reading this what each field means. Please explicitly name each field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be explicit, this is as simple as
DeviceConfig(
name="DYMO LabelMANAGER PC",
deviceIDs=[int(SUPPORTED_DEVICE_ID.LABELMANAGER_PC)],
...
Also, the use here of SUPPORTED_DEVICE_ID
seems very indirect to me. Why not just do
deviceIDs=[0x0011],
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that is possible can you give me a syntax example? (It would definitely be a lot clearer)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FaBjE that was an explicit example, and should work without other modifications to your code if you continue the obvious pattern. (Python lets you specify the names of positional arguments in the function invocation.) Does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn´t see your example at first. I had this page already open I guess.
But thanks! I will try to add.
I added the enum because before the DeviceConfig class I checked with if-else in the code which properties applied to the printer (Based on the device ID), but that was ugly imho.
I agree it is kind of redundant now. Shall I remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an example:
def f(x, y):
return 2 * x + y
f(y=1, x=2) # 5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
""" | ||
Dictonary of print margins per tape size | ||
Entry: TapeSizeMM : (topMarginInPx, bottomMarginInPx) | ||
Use the calibration routine to determine these for each tape size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a link here to a markdown document under /doc
which describes what the calibration routine is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately there is no document at this moment. I think we need to add a link when it is there.
self.name = name | ||
self.deviceIDs = deviceIDs | ||
self.printHeadSizePixels = printHeadSizePixels | ||
self.supportedTapeSizes = supportedTapeSizes | ||
self.marginsPerTape = marginsPerTape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe define the class as @dataclass
. https://docs.python.org/3/library/dataclasses.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'd probably use a NamedTuple
which is simpler, but either should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure of the pro's and con's of both. But I think dataclass is easier to convert to?
I'll give it a shot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made dataclass
if idValue in self.deviceIDs: | ||
return True | ||
else: | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure a function is really needed for this, as it is rather short, and can be replaced with a simple expression. In any case, it should be refactored as follows:
return idValue in self.deviceIDs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix. It seemed more "contained" to me than just accessing the array itself from outside the object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
else: | ||
return False | ||
|
||
def get_tape_print_margins(self, tapeSizeMM: int) -> tuple[int, int]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, tape_size_mm
, not tapeSizeMM
(I will refrain now from commenting of improper casing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do 😉 I'll do a re-check of my work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outdated/fixed
if tapeSizeMM in self.supportedTapeSizes: | ||
if tapeSizeMM in self.marginsPerTape: | ||
# Return specified margins | ||
return self.marginsPerTape[tapeSizeMM] | ||
else: | ||
# No specific margins set, return default | ||
return (0, 0) | ||
else: | ||
# Tape size not supported | ||
raise ValueError( | ||
f"Unsupported tape size {tapeSizeMM}mm. " | ||
f"Supported sizes: {self.supportedTapeSizes}mm" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As coding convention, instead of:
if term:
# some very long logic
else:
raise ...
do:
if term:
raise ...
# some very long logic
This reduces nesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed (now in dymo_labeler.py)
if tapeSizeMM in self.marginsPerTape: | ||
# Return specified margins | ||
return self.marginsPerTape[tapeSizeMM] | ||
else: | ||
# No specific margins set, return default | ||
return (0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return self.marginsPerTape.get(tapeSizeMM, (0, 0))
or even better:
DEFAULT_MARGINS = (0, 0) # put this on top of file
return self.marginsPerTape.get(tapeSizeMM, DEFAULT_MARGINS)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, will fix. (these are the kind of things you have no clue about when using a new language)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outdated
""" | ||
# | ||
for device in SUPPORTED_PRODUCTS: | ||
if device.matches_device_id(idValue) is True: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove is True
. It will work the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, will fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
if foundConfig is not None: | ||
self._deviceConfig = foundConfig | ||
else: | ||
raise ValueError(f"Unsupported device type {self._device.id_product}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As before, style comment. Please use the following convention:
if foundConfig is None:
raise ValueError(f"Unsupported device type {self._device.id_product}")
self._deviceConfig = foundConfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
if foundConfig is not None: | ||
self._deviceConfig = foundConfig | ||
else: | ||
raise ValueError(f"Unsupported device type {self._device.id_product}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you return None
from the function and raise here, instead of raising in the function itself, and then you will not have to test returned value from the function (and change its type to also support None
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question...
Stupid answer: I learned about the raising exceptions later on.
will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
@@ -82,8 +81,11 @@ def find_and_select_device(self, patterns: list[str] | None = None) -> UsbDevice | |||
LOG.debug(dev.device_info) | |||
dev = devices[0] | |||
if dev.is_supported: | |||
msg = f"Recognized device as \ | |||
{SUPPORTED_PRODUCTS[SUPPORTED_DEVICE_ID(dev.id_product)]}" | |||
foundDeviceConfig: DeviceConfig | None = get_device_config_by_id( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need to declare the type of foundDeviceConfig
(I guess you meant found_device_config
:-) )?
If the function declares its return type, then I think it should be fine not to declare here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this was an ugly workaround. Will be obsolete when I fix the other function.
And yes of course I meant snake_case 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Ideally the person making the request (asking a question, requesting a change) resolves. If it's clear that you've decisively addressed the request and no followup is needed, then you can close yourself. It's also fine to leave them open. There are no rules, only common sense and etiquette. |
Move LABELER_DISTANCE_BETWEEN_PRINT_HEAD_AND_CUTTER_MM to device config
Good to know. Makes sense to me, I've left most open as I think my fixed should be check too if they are the desired way. |
As the DPI or PPM (pixels per mm) is now a property of the printer. Move all calculations requiring this value to the labeler object. This will improve future support of different DPI printers
I did a big change for the DPI values. 7246473 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much @FaBjE for your work here.
When I run labelle-gui
with my LabelManager PnP, I get the following stack trace:
qt.gui.icc: fromIccProfile: failed size sanity 2
Traceback (most recent call last):
File "/home/mares/repos/labelle/src/labelle/gui/gui.py", line 108, in _on_settings_changed
height_px=self._dymo_labeler.label_height_px,
File "/home/mares/repos/labelle/src/labelle/lib/devices/dymo_labeler.py", line 277, in label_height_px
return self.tape_print_properties.usable_tape_height_px
File "/home/mares/repos/labelle/src/labelle/lib/devices/dymo_labeler.py", line 305, in tape_print_properties
raise ValueError(
ValueError: Unsupported tape size 24mm. Supported sizes: [6, 9, 12]mm
Aborted (core dumped)
I believe this is indicative of a structural problem that we are introducing here. (Although the rest of the codebase is not innocent.) I strongly disapprove of the use of properties here. Accessing class attributes should be a completely safe operation. (The type checker should guarantee that they're defined.) In the stack trace we see two nested attribute accesses, which is an insane obfuscation. Unless there are exceptional circumstances, attributes should be static and functions should compute stuff. Note also how tape_print_properties
is a huge block of (thankfully well-documented) code.
Instead of having a property called label_height_px
I'd like to instead see an ordinary function get_label_height_as_px
. The benefits to this approach are that:
- we define an honest interface where attributes are attributes and functions are functions
- it makes us actually think about what should be an attribute vs a function instead of lazily reencoding everything imaginable as a property, creating needless layers of indirection
But then this also brings up the issue of whether or not we should even have mm
in the first case.
A tape has:
- height in mm
- height in px
- dpi / pixels_per_mm
and any two determines the third. As far as I can tell, we don't need DPI for anything, and it doesn't seem like we even need height in mm if we treat "tape size" as a label. While we can do some clever stuff with dpi for DYMO printers, I wonder if it's actually worth all this code complexity.
TapePrintProperties = namedtuple( | ||
"TapePrintProperties", | ||
["usable_tape_height_px", "top_margin_px", "bottom_margin_px"], | ||
) | ||
"""Tape print properties tuple type. | ||
Contains margins and size in pixels for printing. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this definition and recreate it outside the class Dymolabler:
block as follows:
class TapePrintProperties(NamedTuple):
usable_tape_height_px: int
top_margin_px: int
bottom_margin_px: int
I'd recommend omitting the docstring since it doesn't carry any information that's not already evident from the current names / types.
|
||
|
||
class PrintPreviewRenderEngine(RenderEngine): | ||
X_MARGIN_PX = 80 | ||
Y_MARGIN_PX = 30 | ||
DX = X_MARGIN_PX * 0.3 | ||
DY = Y_MARGIN_PX * 0.3 | ||
dymo_labeler: DymoLabeler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like we are attaching a DymoLabeler
to lots of objects and passing it around in a lot of arguments. But as far as I can see, we only ever need to access the tape height in either px or mm.
Couldn't we just define a NamedTuple called TapeHeight with two attributes px
and mm
, and pass this around instead?
Ok, I'm going to be brutally honest here. Not that I want to, but I think it needs to happen.Up until this point I liked to contribute something to labelle and getting my label printer to work under linux. The current codebase is/was not made for printers with different properties. (Supported tape sizes, dpi, etc) I tried to integrate this, but I needed to touch a lot of stuff. Trying to to change as little as possible (within reason) I did this. But there is a lot of stuff I think that can be handled different/better with the things we know now. Some may be personal preference or coding style preference. But I think we are stuck in a no-mans-land between the two at this point. As I said in the opening post of this PR this code is not perfect. There are issues with it, mostly caused by my changes "conflicting" the current setup of the codebase. Let me address the points:
Yes, this is caused by a structural problem the GUI has. For starters: I didn´t notice this as my printer supports all tapes. More low level: when the printer properties are loaded (like supported tape sizes) the change-event of the control fires every time a item like a tape size is added/removed. It starts with the default (simulator) config. (which has all supported tape sizes) Than the USB printer is initialized and "attached" to the GUI. Than the weird stuff/problems starts to happen. The "backend" already has the USB printer. But the GUI controls still have the old data. Change events fire, and the GUI tries to set properties (left over from the default) that the printer doesn´t support. So boom, exception. IMHO: The change events should not fire when the printer properties are being loaded.1. Disable change events, 2. Clear GUI controls, 3. Load new printer properties to controls, 4. Enable change events.
For starters, this Second, there already were a lot of properties that do either the same or something similar to eachother. Third, I was specifically asked to make this a property. #54 (review) If you are 100% sure you want it a function I will convert it back to a function, but that will be the last time I'm changing it.
Correct
No. The PRINTER has a height in px for a tape. There are no pixels on a tape.
No. The PRINTER has a DPI.
You need DPI for the GUI (margins display) and CLI, calculations of tape size in pixels (for a particular printer) If I were to setup this code today, I would make the two of classes of After that, pass the printer object to all classes that needs to to something. Like the renderers, GUI etc. They can retrieve the data they need from the printer object. They may need some more input, like for example a text or barcode that needs to be rendered. But all printer/tape specific data can just be retrieved from the printer object instead of passing around lots of separate variables. Well, these are just my two cents. |
I suggested it, but I am not orthodox about it, and I do accept the reasoning raised earlier, as to why this was a bad suggestion. I am sorry for causing you to waste your eneregy, and lose some motivation to work on this codebase. |
Hi @FaBjE, I'm really sorry that my criticism caused frustration and discouragement. I completely agree with everything you wrote. My criticism of the overuse of properties is more about the existing codebase and the need to change course rather than your contribution in particular. It was not so tactful for me to single out your code as an example of why property overuse leads to confusion. Especially since the previous review requested use of properties. As you have realized, there are still a lot of structural issues in the codebase. A lot of things snuck in because I didn't review code as carefully as I should have. So it's a challenging situation. I want to be encouraging of contributions from people like you. But I also want the code to remain maintainable, and avoid further entrenching our previous bad design decisions. You ask me to lay out a vision, but I think you already lay out a very clear vision in your last comment. The one thing I'd adjust is to avoid the idea of a monolithic printer object. I'd rather design things so that we have isolated stages, each of which only requires limited information. (Like the Linux philosophy of piping together simple commands.) To figure out what these isolated stages should look like, let's suppose we were designing a web client as a replacement for the GUI. I think the first thing we'd need is a high-level specification of the label elements (e.g. text, barcode, etc.) that are to be horizontally combined. These shouldn't depend on the tape size or device, and it should ideally be easily serializable. Next would come the rasterization step that, for a given tape size in pixels, converts the elements into a bitmap to print. Finally, with the bitmap in hand, it should be passed to the printer for printing. So there are these steps: element specification, rasterization, printing to device, which I'd like to see as isolated as possible. As for concrete next steps for this PR, let's try and make sure things are running without any errors. Let's try within reason to reduce the amount of stuff that's being added to interfaces and being passed around. And for these structural problems, let's keep track of what's wrong, and build in a way that will facilitate eventually fixing those structural problems. And I'll try and to better to keep things fun. I think we're all here to build cool stuff in a relaxing environment. Sorry if I was being too up-tight. I really hope that you stick around. |
Since the project has a new name and a new scope (all label printers, ideally), it makes sense to define a sustainable architecture and overhaul it, thus bump the major release number. This might cost us less work in the long run, as with a few Dymos we already struggle to keep it tidy. @maresb's general idea has some merit. I think that each printer model should be a different "object", with its own renderer code, attributes and so on. One thing that worries me about this proposed approach is the second stage that composes the pieces together. I am thinking about barcodes and QR codes in particular, that will not work if scaled improperly. Thus this stage has to know how many dots it's working with, something that definitely is the domain of the third stage. I guess we'll need a graph, and a space for this discussion :) |
Right, the pipeline can't be strictly one-way. In pseudocode I'd propose something like: def print_label(label_specification, device, tape):
bitmap_height = device.usable_tape_height_px(tape)
bitmap = rasterize(label_specification, bitmap_height)
device.print(bitmap, tape) Of course it probably will end up more complicated than this in practice due to implementation details. So for this PR I think my specific question would be if, instead of passing around the |
I would not call it a bad suggestion. Because every other thing is put into a property in the dymo labeler class.
Well, you didn´t exactly put my code on the spot as the propery already existed. (I only renamed it).
Yes, but what you are currently doing now from my perspective is criticizing stuff out of scope of this PR.
Well, yes, but that is my vision. I have no clue in where you are standing on this or what the goal of the project is. My advice on your view would be that it might be the most ideal design. (keeping stages separate) But be realistic and keep it simple. This is at the moment a tiny project with about no-one with time to code on it. You will introduce a abstraction and challenges with it. Which all require effort to code.
Brutally honest, I (would like to) see labelle as the simple "simple but functional"/no-nonsense portable application that came on the Dymo PnP labeler (for windows) and I think you are 80% there atm. An example that I read in the GUI issue is about the office setting. But not to ruin your dreams or anything. I think labelle's strength will be in supporting the "older" hardware. and keeping it alive. reducing e-waste. Again, it may be fine ambitions, but something (too) far in the future imho. On the pratical side as @tomek-szczesny pointed out in it's next post, the bitmap that needs to be rendered is direct dependent on the printer (dpi) and label (size).
I have a different suggestion for this PR. I would like to see a "develop" branch. A separate overhaul of the design may be implemented on top of that. Maybe just in tiny steps to keep it manageable. (again, amount of available coders/time)
The dymo labeler object is current passed around for the DPI. Label height is already passed around. So again my view. Merge this to develop, agree on a global design, rework everything (in tiny steps), have a proper codebase. |
I'd be happy to do this. That way we can follow up separately with the structural issues I've raised before merging to I just work on this project to screw around. I have no serious ambitions. For quite a while I was the only active maintainer, and I was just trying to keep the project alive. I do have things I'd like to eventually see implemented, like a web client and server, but unfortunately I don't have the free time to implement that stuff myself. Also, to be honest I don't understand the details of how all these classes are implemented, and whenever I look at the details I'm surprised and confused. It used to be way worse until @tomers came along and rewrote major portions of the codebase. He took us from 20% to 80% of having a stable functional app. |
Closes #51
I have added a DeviceConfig object that contains a configuration for each type of printer/labeler.
I started out with the lookup table, but puzzeled by the "random" margins I figured it out how the principal works.
For every printer we now need a "print head size in pixels" and "print head active size in mm" the rest is calculated.
I think there might only be two versions (128px/18mm and 64px/9mm).
Based on my scans we determined a label-position inaccuracy of 0.7mm. I rounded it up to 1mm to use as safety margin for now.
I´d like to hear your thoughts. Bear in mind that this is my first python code. So you may find a lot of weird things...
This is not done yet, so only a draft PR.
I've tested it on my LabelManager PC II and a LabelManager PnP both can print valid labels 12mm (and 24mm on the PCII)
I have identified a couple of issues so far: