Repository navigation
Conversation
25e414d to
76be6ba
Compare
|
I went over this again against the existing architecture and added a few commits on top (no force-push), so you only need to look at these:
No .po/.pot changes. |
|
Thank you for the PR. Can you explain the reasoning behind threading this through the doc model, workpiece model, all the way to the sketcher? This seems like a highly invasive architecture for a sketcher-specific feature. |
a5d3f9b to
e28cfe2
Compare
|
Good question — the short version: the counter is document state, the value a
The sketcher ( Why not resolve at export time instead of threading it down: geometry is cached Why the counter advances through job signals ( Happy to move any of the three somewhere narrower if you would rather see the |
|
I am not sure that it makes sense for the document to own this data. Serial numbers are not necessarily project specific - likely manufacturers want serial numbers to be unique across their business and/or product line. I would imagine that to assign serial numbers, an app-global data source management makes more sense - likely as an addon - that allows to define CSV, database connectors, REST API endpoints and other sources, and provides an API that templates build on. But this also requires careful thinking about how actual allocation happens process-wise, not just API and data ownership wise. When should the app assign a new serial number? When should it overwrite an already assigned one? How different would the project need to be for reallocation to happen? Should it be manual? Or for every job sent to the machine? ... And this question also touches your other PR for QR code generation: QR codes do not just support serial numbers, but general product information, too. So what would a process look like where a company wants a QR code that contains multiple attributes, say, a serial number, a production date, and a hard coded supplier name? That's why I feel like having a proper data management addon seems warranted here. So I am not convinced that a document-level serial number handling should exist. I think to properly support this, we should define a couple of use cases, then an architecture, and then an iterative implementation plan. While I like the "small iterative step" approach in general, in this case a step in the wrong direction could commit us into a situation where we would have to support document-level data in the long term. |
|
The "unique across the business" case is real, but I think it is the minority one, and I would not want the simple case held back by it. Every piece of software I use with serial numbering for printing or engraving works on file level: the number lives in the file, is saved with it, and when I reopen the job it shows the number that was actually produced. That is the part that matters to me — the file is the record of what went out the door. Reopen last month's job and the serials on it must be the ones that were engraved, not whatever the counter says today. I have thought about the alternatives you raise, and neither convinces me: An app-global source makes sense for a manufacturer with a product line, but for the people who will actually use this — a shop engraving parts and tags — requiring a data-management addon with CSV, database and REST connectors just to get an incrementing number is a lot of machinery for the common case. And I would not want it on machine/firmware level at all: controllers get swapped, jobs move between machines, and state that lives on the machine is not in the file. That may make sense in a few setups, but in most it doesn't. On your process questions: in the file model, allocation is simple and visible — the counter advances when a job finishes, which is the moment a physical part with that number exists, and if a batch needs re-cutting, the user sets the number back in the Variable Text dialog. The resolved value is stored per workpiece, so what you reopen is what you actually ran. And the QR concern goes the same way: a QR carrying a serial plus a production date plus a supplier name is a template that composes those values — the date and the supplier string are file-level values too. Nothing there needs a global data store either. So I would not read this as committing us to document-level data forever. The template API is the extension point: a future data-management addon can be an additional provider feeding the same values, and the document-level counter is simply the default when no provider is configured. The other way around — waiting for the addon before serials exist at all — means the simple case may never ship. Use-case-first is fine with me: this PR is one concrete use case, an incrementing counter that lives with the file and advances per finished job. I would rather land that and treat a data-source addon as its own track than block this on it. |
|
Putting this into the document inevitably leads to the possibility of serial number collisions - this is the part that I am not willing to commit to. I agree about the QR template, but here my concern is the same: Defining the template in the project would inevitably violate the single source of truth principle. If I ever want to update my company's QR template, I do not want to re-edit every project I ever created. Overall, this is a feature that I would like to use myself, but if there is no real uniqueness guarantee because producing something from an old project file could re-yield the same serial number... I don't think this would be a safe way to handle it. That said, one thing I could agree to is having a document-level job counter, and that counter could be passed to macros and to the sketcher templates to use as they see fit. But I would see this as a concept separate from any serial number feature, and the document would only carry one counter, not arbitrary variables. By separating these concepts I see no risk to conflicts with potential future serial number and data source management addon. This should at least resolve the immediate intent of this current PR; the question of where QR templates are managed could be decided separately. |
|
That works for me — a document-level job counter is exactly the kind of thing I meant: one number that lives in the file, is saved with it, and is available to the templates. How it is labelled is then the user's business: a template that says "SN {counter}" gives me serial numbering, and the uniqueness policy stays mine, not the app's. That is also how the engraving and printing software I use behaves — the file carries the counter, and it is on me not to reuse a batch. So let me reshape this PR along your lines:
Out of scope, as you say: serial-number semantics, QR-template management and any data-source addon. Those can be designed separately, and this counter does not stand in their way. I will update the branch and the PR title and description to match. |
Text templates can use {serial}, formatted like any other value
({serial:04d}, SN-{serial:05d}). The counter (current, start, step,
optional end with wrap, auto-advance) is stored in the project.
Each workpiece has a variable offset: a copy with offset n shows the
value n steps after the current one, so array copies can carry
consecutive numbers. When the counter changes, only workpieces whose
templates read a document variable regenerate, keeping their scale.
Refs barebaric#527
DocEditor.variable_text changes the counter (settings, Next, Previous, Reset) through the undo history, so the document is marked modified and an accidental change can be undone. Number Copies gives selected items the offsets 0, 1, 2, ... in reading order, and grid and circular arrays give their copies increasing offsets. Refs barebaric#527
MachineCmd now knows whether a send job ran to its end: cancelling marks the running job, and the monitored run reports completion. Only then, and not for a pointer dry run, the editor advances the counter by one step per numbered copy. Framing and failed jobs never advance it. An optional setting also advances it after a G-code export, for jobs that run from a memory card. Refs barebaric#527
Tools > Variable Text opens a dialog with the counter settings, Previous / Next / Reset and a preview of the numbers the next job uses. Arrange > Array > Number Copies numbers a selection in reading order, and the workpiece properties show the variable offset of a template workpiece. Refs barebaric#527
MachineCmd and FileCmd no longer call the serial counter directly. MachineCmd emits job_completed after a send that ran to its end (not for framing or pointer dry runs) and FileCmd emits gcode_exported. VariableTextCmd and the main window subscribe to them, so other features (e.g. CSV merge, barebaric#528) can hook into finished jobs too. Refs barebaric#527
Use Adw.ToolbarView with a scrolled preferences page and the step settings dialog's default width, and move Previous / Next / Reset into the preview row instead of a loose button box under the group. Refs barebaric#527
Doc, ArrayCmd and VariableTextCmd each had their own copy of the "item itself or its workpiece descendants" lookup. Refs barebaric#527
The fallback that parsed the resolved text cache was never reached: template values need a document, and then the provider is available. Refs barebaric#527
Doc now carries _job_counter (class JobCounter in core/job_counter.py) instead of the surviving _variable_text, with the serialized key, the commands, the dialog, the property provider and the win.job-counter action renamed to match. Docs headings, the Tools menu entry, the property-panel group and the seven translations follow. The docs URL slug stays features/variable-text for link stability. Reported in barebaric#554: some of the previous naming had survived the first counter rework.
|
Done — the renaming now goes all the way down.
The docs URL slug stays Rebased on current main; 262 doceditor tests green (80 covering this feature). |
CI's Lint job failed on 2a9d900: __all__ kept VariableTextPropertyProvider's old position after the rename, and the job_counter modules shifted seven import blocks out of order.
knipknap
left a comment
There was a problem hiding this comment.
Thanks for the update. I gave it a full review this time.
| @@ -0,0 +1,172 @@ | |||
| """ | |||
| Document-level variables for text templates. | |||
|
|
||
| class JobCounter: | ||
| """ | ||
| The variables a document offers to text templates. |
There was a problem hiding this comment.
Fixed — the class docstring now describes the plain counter instead of the old variable-text wording.
| return instance | ||
|
|
||
|
|
||
| def _names_in_expression(expression: str) -> set[str]: |
There was a problem hiding this comment.
The job counter should not have expression knowledge
There was a problem hiding this comment.
Moved out — _names_in_expression and referenced_template_names now live in rayforge/core/expression/placeholders.py, next to the rest of the expression machinery.
| return {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)} | ||
|
|
||
|
|
||
| def referenced_template_names(templates: Iterable[str]) -> set[str]: |
There was a problem hiding this comment.
The job counter should not have template knowledge
There was a problem hiding this comment.
Same move: the helper is gone from the counter module; JobCounter only knows its own name and values now.
| def template_sources(self) -> list[str]: | ||
| """The raw template strings the geometry provider renders.""" | ||
| provider = self.get_geometry_provider() | ||
| get_sources = getattr(provider, "get_template_sources", None) |
There was a problem hiding this comment.
Getattr implies uncertainty that should not exist here
There was a problem hiding this comment.
Removed — get_template_sources() and a new variables_used() are part of the IGeometryProvider protocol now, so the workpiece calls them directly instead of probing with getattr.
|
|
||
| def _world_provider_params(self) -> dict[str, Any]: | ||
| """ | ||
| Parameters for a detached copy: the template variables are baked |
There was a problem hiding this comment.
"the template variables are baked in only when this workpiece's templates read them"
This is a leaky abstraction. The workpiece should not care what the sketch uses or doesn't use. It should just pass built-in variables like the job counter unconditionally. It should not inspect templates.
There was a problem hiding this comment.
Reworked — no template inspection remains in the workpiece: it passes the counter values unconditionally, and in_world bakes them into every detached copy. The copies-count policy in Doc asks the provider via variables_used() instead of parsing templates.
| """ | ||
| Regenerates the geometry after a template variable changed. | ||
|
|
||
| Only workpieces whose templates read one of the document's |
There was a problem hiding this comment.
If a workpiece wants to avoid unnecessary recalculations when the job counter increases, then the sketch itself should abstract the knowledge about "change" away, i.e. the sketch should answer the question of "given these new variables, do I actually need to recalculate"? Outside the sketch, knowledge about what may or may not cause a change should not exist.
With proper handling in the sketch, I would expect near zero changes in workpiece.py compared to main, as all it would do is pass the job counter to the sketch along with all other variables/parameters in a generic way. All the special casing for "world workpiece" and other changes in this file would disappear.
There was a problem hiding this comment.
Implemented in the sketch: Sketch.get_geometry reuses its previous result while the effective parameter values are unchanged, and drops that cache whenever the sketch itself changes (updated). refresh_template_values is unconditional now, and compared to main the workpiece only gained the generic template_values / _provider_params pass-through.
There was a problem hiding this comment.
The UI probing into specific variables in a sketch seems like too much. Variables are intentionally generic, this implementation weakens this.
I wonder also why it is needed: If a sketch DOES use the counter in a template, wouldn't it be visible in the workpiece anyway? The property provider then seems redundant.
There was a problem hiding this comment.
The probing is gone — the provider handles any workpiece selection now. It exists because the variable offset is per-copy data with no other editor UI (the row also hosts the shortcut to the counter settings). If you'd rather remove it entirely, say the word and I'll drop it.
There was a problem hiding this comment.
Renamed — the page lives at website/docs/features/job-counter.md now, with the sidebar entry, the links from expressions.md and all seven translation mirrors updated.
Following the review: the job counter is a plain counter again - the expression and template-name helpers move to rayforge.core.expression.placeholders. The geometry provider protocol now carries get_template_sources() and variables_used(), so the workpiece stops probing providers with getattr and stops inspecting templates; doc and workpiece pass the counter values unconditionally, and in_world bakes them in for every detached copy. The sketch answers the recalculation question itself: get_geometry reuses its previous result while the effective parameter values are unchanged, and drops that cache whenever the sketch changes, so a counter step does not re-solve sketches that do not read it. The property provider no longer inspects templates either, and the docs page lives at its final name, features/job-counter.
|
All eight points are addressed in 45a86cf — details in the individual threads. Summary: the counter is a plain counter (template/expression helpers moved to |
pyright checks structural conformance to IGeometryProvider; the fake providers in the workpiece, wavefront and offset-fill tests now implement get_template_sources() and variables_used().
45a86cf made Sketch.get_geometry reuse a result for the same parameter values, but the key ignored the resolved text the caller seeds and the cache survived edits that only re-solve the sketch: - two workpieces of one sketch shared one {uuid4()} value, because the second caller got the first caller's geometry and text back; - after a text box was edited and solved, the old text kept rendering until something sent the updated signal. The key now includes the seeded text. A result is also stored under the request's own key only when it is reproducible, i.e. no text was newly resolved from a template function, so every new caller still gets its own uuid. solve() and apply_undo_state() drop the cache like the updated signal does. in_world no longer writes the document's values into the copy's geometry_provider_params, which stay the instance's own overrides as on main. The copy carries them as the values its geometry uses, and a workpiece outside a document renders with those values.
The geometry cache key held every value a caller passed, so a job counter step still re-solved every sketch, including the ones whose templates and expressions never mention the counter. The key now only holds the names the sketch reads: its input parameters, its parameter and constraint expressions, and its text templates. The sketch thus answers on its own whether new variables change its geometry.
A workpiece normalizes the geometry and fills it gets from its provider in place, so the geometry cache held the normalized unit square after the first caller and gave it to the next one: a second workpiece of an unchanged sketch came out 1 x 1 mm. The cache now stores and returns copies, and the tests count solves instead of comparing object identity.
Workpieces now ask their provider for a reference point (main's anchor modes), so the fake provider needs the method too. Refs barebaric#527
A counter step regenerates the text and kept the scale, but resized around the center. It now resizes around the workpiece's anchor point, like a sketch edit does since main's anchor modes. Refs barebaric#527
After the merge with main it sat in the released 1.13.0-beta1 section, with a stray caret in front. Refs barebaric#527
The group and the preview row still said "Serial Number(s)" after the rename to a plain job counter. Refs barebaric#527
Pyright flagged it after the merge: the provider protocol now has get_template_sources() and variables_used(). Refs barebaric#527
A provider that inherits the protocol without implementing variables_used() gets None back, and job_counter_copies() raised a TypeError on it. Barcodes from barebaric#529 are such a provider until they read template names themselves. Refs barebaric#527
barebaric#554 renamed the template variable to {counter} and the docs page to features/job-counter. The content tooltip, the tests and the Barcodes page in all languages follow. Refs barebaric#529
barebaric#554 adds get_template_sources() and variables_used() to the provider protocol; with them the fake keeps passing pyright whichever PR lands first. Refs barebaric#529
This reverts commit cf3c4a8.
A counter change cleared the resolved text of every workpiece, so a
{uuid4()} or {today()} got a new value, also in sketches that do not
read the counter, and undo did not bring the old one back.
The sketch now records with each resolved text the values of the
variables it read, and re-resolves a text only when one of them
changed. The workpiece keeps its resolved text, and the document only
refreshes workpieces whose provider reads the counter.
With that, the workpiece no longer bakes template values into detached
copies: it just passes the counter for its offset to the provider.
Refs barebaric#527
The module, Doc and the docs page still spoke of variables the project offers; the translations kept a "serial numbers" heading. The docs also no longer point to a Variable Offset row in the properties panel. Refs barebaric#527
It showed on every workpiece and repeated what the canvas shows. Arrays and Arrange > Array > Number Copies set the variable offsets; the counter itself lives in Tools > Job Counter. The command that only the panel used goes too. Refs barebaric#527
Number Copies used the private LayoutStrategy._get_item_world_bbox; DocItem.bbox gives the same world box for workpieces and groups. Refs barebaric#527
|
I merged main into this branch (a merge commit, no rebase) and went over it again. New commits, up to b5c3193:
One change in an existing test: Lint and pyright are clean; 7739 backend, 1142 UI and 18 stress tests pass. Note for #556: whichever of the two lands second needs |
What does this PR change?
Adds one project-level job counter that text templates can use (#527). The user labels it in the template:
No. {counter:03d}orSN-{counter:05d}. The app makes no uniqueness promise, as agreed in the discussion: keeping numbers unique across a business stays the user's process..ryp.current + offset × step. Arrange → Array → Number Copies and grid/circular arrays give their copies increasing offsets.step × copiesonly after a send job ran to its end: not for framing, dry runs, failed or cancelled jobs. Optionally it also advances on G-code export (off by default), for jobs run from a memory card.Not in scope: arrays inside one sketch (all entities share one workpiece and template context), arbitrary document variables, and serial-number semantics.
Why did you choose this specific architectural approach?
Docholds one plainJobCounterand each workpiece holds only its offset.rayforge.core.expression.placeholders). The workpiece just passes the values to its geometry provider, and the sketch decides whether its geometry depends on them.{counter}, and the sketch caches its geometry keyed on the values it actually reads. Each resolved text remembers the values it was resolved with, so{uuid4()}or{today()}keep their value through a counter step and its undo. Cached geometry is handed out as copies, so workpieces can scale it independently.MachineCmdandFileCmdinstead of being called from them.job_finishedalso fires for framing, cancel and errors, soMachineCmdnow reports whether a send ran to completion.How did you manually test this locally?
No. {counter:03d}; close the sketcher. Tools → Job Counter…, set Current Value to 7: the canvas shows "No. 007". Next shows 008; Ctrl+Z goes back to 007.{uuid4()}placed twice: the two copies show different values, which stay the same when moving, zooming, after save + reopen, and through Next and Ctrl+Z of the counter.{counter}doesn't change.Checklist
this code.