Skip to content

feat: job counter variable for text templates - #554

Open
switch87 wants to merge 34 commits into
barebaric:mainfrom
switch87:feat/serial-numbers
Open

switch87 wants to merge 34 commits into
barebaric:mainfrom
switch87:feat/serial-numbers

Conversation

@switch87

@switch87 switch87 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

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} or SN-{counter:05d}. The app makes no uniqueness promise, as agreed in the discussion: keeping numbers unique across a business stays the user's process.

  • The counter (current, start, step, an optional end that wraps to start, auto-advance) is stored in the .ryp.
  • Each workpiece has a variable offset, so copies show current + offset × step. Arrange → Array → Number Copies and grid/circular arrays give their copies increasing offsets.
  • Next / Previous / Reset and the settings go through the undo history (Tools → Job Counter…).
  • When a new value makes the text wider, the workpiece keeps its scale, and its anchor point (Position relative to) stays in place.
  • The counter advances by step × copies only 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?

  • Counter location: the counter is document state and the offset is per-copy state. Copies are workpieces, so Doc holds one plain JobCounter and each workpiece holds only its offset.
  • No template knowledge outside the sketch: following your review, the counter knows nothing about templates or expressions (those helpers live in rayforge.core.expression.placeholders). The workpiece just passes the values to its geometry provider, and the sketch decides whether its geometry depends on them.
  • Re-solving only what reads the counter: the document refreshes only workpieces whose provider reads {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.
  • Advance trigger: advancing hangs off job/export signals emitted by MachineCmd and FileCmd instead of being called from them. job_finished also fires for framing, cancel and errors, so MachineCmd now reports whether a send ran to completion.

How did you manually test this locally?

  1. New sketch with a text box 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.
  2. Place the sketch twice and use Arrange → Array → Number Copies: the copies show consecutive numbers and both stay full size.
  3. A second sketch with {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.
  4. Edit the first sketch's text to something longer and close the sketcher (the workpiece grows), then Ctrl+Z: text and size go back.
  5. Run a job (or export G-code with "Advance After G-code Export" on): the output has the number shown, the counter then moves on by the number of copies, and a sketch without {counter} doesn't change.

Checklist

  • I am not an AI. I have manually read the code and understand it.
  • I take full responsibility for the quality and architectural fit of
    this code.
  • I agree to publish this code under the MIT license.

@switch87 switch87 mentioned this pull request Oct 12, 2026
3 tasks done
@switch87 switch87 changed the title WIP: serial number variable for text templates feat: serial number variable for text templates Oct 12, 2026
@switch87
switch87 force-pushed the feat/serial-numbers branch from 25e414d to 76be6ba Compare October 13, 2026 15:16
@switch87
switch87 marked this pull request as ready for review October 14, 2026 09:04
@switch87

Copy link
Copy Markdown
Contributor Author

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:

  • 4f54bca: MachineCmd and FileCmd no longer call the serial counter directly. They emit job_completed / gcode_exported and the counter subscribes, so the machine code doesn't know about this feature (and CSV merge can use the same hook later).
  • 69aeeeb: the Variable Text dialog now uses the same layout as the step settings dialog (ToolbarView, scrolled page, width 600), and Previous / Next / Reset sit in the preview row.
  • 9ac310c: one shared helper for "the workpieces of an item" instead of three copies.
  • 98482d6: removed a fallback in WorkPiece.template_sources that was never reached.
  • a5d3f9b: the docs page translated into all website languages.

No .po/.pot changes.

@knipknap

Copy link
Copy Markdown
Contributor

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.

@switch87

Copy link
Copy Markdown
Contributor Author

Good question — the short version: the counter is document state, the value a
particular copy shows is per-copy state, and copies are workpieces. Here is why
each layer holds what it holds.

Document.variable_text (rayforge/core/doc.py) — one serial counter per
file. It has to live at document level because the user edits it in the
Variable Text dialog, it must survive save/load, and a finished job advances it
no matter which item was cut. Putting it lower would mean one counter per item.

WorkPiece.variable_offset (rayforge/core/workpiece.py) — a copy of a
design is a workpiece (an array instance). Two copies share one sketch, so the
sketch simply cannot know which copy it is rendering; the offset has to live
where the copies live. WorkPiece.template_values() composes the document
variables with this copy's offset, and _provider_params() hands that to the
geometry provider the same way every other provider parameter is handed over.
The offset and the resolved values are part of the workpiece state, so a saved
file reopens showing the numbers it was cut with.

The sketcher (sketcher/core/sketch.py) — 8 lines, and none of them know
about serials: get_template_sources() returns the raw text-box contents so the
document can tell whether a workpiece uses variables at all. Everything else is
the sketcher rendering the text it is handed, exactly as it renders a literal
string; template substitution in that path is pre-existing
(_resolved_text_cache).

Why not resolve at export time instead of threading it down: geometry is cached
per workpiece and the cache records which values produced it
(_template_values_used). An advancing counter therefore invalidates exactly
the copies whose number changed. A separate export-time pass would either
bypass that cache and re-render everything on every job, or leave the preview
showing a number that is not the one being burned.

Why the counter advances through job signals (job_completed in
rayforge/machine/cmd.py) rather than inside the machine layer: "a job ran to
the end" is editor-level knowledge, and the machine layer only emits the signal
— it never learns that a serial number exists.

Happy to move any of the three somewhere narrower if you would rather see the
offset composed elsewhere — say the word and I will rework it.

@knipknap

knipknap commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

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.

@switch87

Copy link
Copy Markdown
Contributor Author

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.

@knipknap

Copy link
Copy Markdown
Contributor

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.

@switch87

Copy link
Copy Markdown
Contributor Author

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:

  • the document carries one counter — no arbitrary variables, no serial semantics, no uniqueness claim;
  • it advances when a finished job ends, and optionally on export for memory-card workflows;
  • the per-workpiece offset stays, so copies of one design show consecutive values;
  • the sketcher templates consume it as they do now; if the automation steps can read it the same way, good — say where you would rather have it and I will follow.

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.
@switch87

Copy link
Copy Markdown
Contributor Author

Done — the renaming now goes all the way down.

  • Doc carries _job_counter (class JobCounter in core/job_counter.py) instead of the surviving _variable_text; the serialized key is job_counter.
  • Commands (JobCounterCmd), dialog (JobCounterDialog, title "Job Counter"), property provider, and the win.job-counter action are renamed to match.
  • Docs headings, the Tools → Job Counter… menu entry, the property-panel group and all seven translations follow.

The docs URL slug stays features/variable-text for link stability — say the word and I will move it too.

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 knipknap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I gave it a full review this time.

Comment thread rayforge/core/job_counter.py Outdated
@@ -0,0 +1,172 @@
"""
Document-level variables for text templates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale docstring

Comment thread rayforge/core/job_counter.py Outdated

class JobCounter:
"""
The variables a document offers to text templates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a stale docstring

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the class docstring now describes the plain counter instead of the old variable-text wording.

Comment thread rayforge/core/job_counter.py Outdated
return instance


def _names_in_expression(expression: str) -> set[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job counter should not have expression knowledge

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread rayforge/core/job_counter.py Outdated
return {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)}


def referenced_template_names(templates: Iterable[str]) -> set[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job counter should not have template knowledge

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same move: the helper is gone from the counter module; JobCounter only knows its own name and values now.

Comment thread rayforge/core/workpiece.py Outdated
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getattr implies uncertainty that should not exist here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread rayforge/core/workpiece.py Outdated

def _world_provider_params(self) -> dict[str, Any]:
"""
Parameters for a detached copy: the template variables are baked

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread rayforge/core/workpiece.py Outdated
"""
Regenerates the geometry after a template variable changed.

Only workpieces whose templates read one of the document's

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale document name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@switch87

Copy link
Copy Markdown
Contributor Author

All eight points are addressed in 45a86cf — details in the individual threads. Summary: the counter is a plain counter (template/expression helpers moved to rayforge.core.expression.placeholders), the provider protocol carries get_template_sources() and variables_used(), workpiece and doc pass values unconditionally with zero template inspection, and the sketch itself answers the recalculation question by reusing its geometry while the effective parameter values are unchanged. Docs page renamed to features/job-counter. Lint clean, 147 targeted tests green.

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
switch87 added a commit to switch87/rayforge that referenced this pull request Oct 21, 2026
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
switch87 added a commit to switch87/rayforge that referenced this pull request Oct 21, 2026
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
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
@switch87

switch87 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

I merged main into this branch (a merge commit, no rebase) and went over it again. New commits, up to b5c3193:

  • The text cache in the sketch now remembers the values each text was resolved with. A counter step only re-resolves texts that read {counter}, so {uuid4()} and {today()} keep their value through Next and Ctrl+Z. Before, every sketch got a new uuid on each counter step.
  • The workpiece no longer clears its resolved text; the document only refreshes workpieces whose provider reads the counter. workpiece.py is smaller than before, what remains is the offset, passing the values to the provider, and keeping the anchor point in place when the text gets wider.
  • The stale "variables for text templates" docstring you pointed at is really gone now, together with the same wording in doc.py and the docs. Sorry, my earlier "fixed" was wrong.
  • The Job Counter group in the properties panel is removed, as you found it redundant. Offsets come from arrays and Number Copies.
  • Copies are ordered by the public item.bbox instead of a private layout helper.

One change in an existing test: test_sketcher_workpiece.py reads [:2] of a cache entry, because the entries now carry a third element.

Lint and pyright are clean; 7739 backend, 1142 UI and 18 stress tests pass.

Note for #556: whichever of the two lands second needs BarcodeAsset.variables_used(). I'll add it in that merge.

This branch was previously deployed

1 inactive deployment
pypi — b5c31938 Deployed Oct 21, 2026 by switch87 via Build and Publish Wheel #2433
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants