polyhedron(vnf), and object()'s single-element delete entry - #106
Merged
Conversation
[vertices, faces] is what every BOSL2 function passes around and what
obj.vnf holds, so it should go straight in rather than having to be split
apart at the call site. All four of these are now the same call:
polyhedron(obj);
polyhedron(obj.vnf);
polyhedron(obj.vertices, obj.faces);
polyhedron(spheroid(d=30)); // any BOSL2 VNF
Only considered when `faces` was not supplied separately, so the
two-argument form always wins and can never be reinterpreted.
The discriminator is BOSL2's own is_vnf test: a VNF's second element is a
list of FACES, i.e. a list of lists. A points list that happens to have
length 2 has a POINT there instead -- bare numbers, not lists -- so it is
left alone and still errors as the invalid polyhedron it is. Two points
cannot describe a polyhedron anyway, so nothing legitimate is taken over.
Verified both directions against real BOSL2: polyhedron(spheroid(...))
matches the volume vnf_polyhedron() produces, and vnf_polyhedron(obj.vnf)
accepts ours.
Minor, not patch: new accepted argument shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
a = object(a=42, b=53, c=8);
b = object(a, [["d", 18], ["b"]]); // -> { a = 42; c = 8; d = 18; }
An unnamed list argument holds entries, and [key, value] SETS while the
single-element [key] DELETES. Only the two-element form was implemented, so
any script using a delete got undef for the whole call -- silently, with no
diagnostic pointing at why.
Checking the rest of the semantics against OpenSCAD 2026.02.01 turned up
more than the reported gap:
- deleting an absent key must be a silent no-op (was: undef)
- every malformed entry must WARN as well as yield undef (was: silent)
Deletion removes the key outright rather than blanking it, which is
observable because ValueObject is insertion-ordered and oscEqual is
order-sensitive: object(a, [["b"], ["b", 99]]) puts b LAST, while
object(a, [["b", 99], ["b"]]) has no b at all. Both are pinned.
The warning text is quoted verbatim from the reference, including two of
its own inconsistencies -- the "not a list" case puts spaces inside its
parens where every other case does not, and the "unnamed argument" case
ends with a trailing space. Reproducing them is the point: these strings
are what a user greps for.
All 19 cases (values AND warnings) were diffed character-for-character
against the reference binary with --enable=object-function; the diff is
empty. mergeObjectArgs gained an Evaluator and a position so it can warn,
which both call sites -- the interpreter's builtinObject and the VM's
Op::CallFn -- now pass through.
967 tests pass under both engines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
revarbat
added a commit
to BelfrySCAD/BelfrySCAD
that referenced
this pull request
Aug 21, 2026
Brings polyhedron(vnf) and object()'s single-element delete entry (BelfrySCAD/openscad_cpp_evaluator#106), both of which this release's viewers assume. Locked only AFTER PyPI's index listed all 7 files, rather than the moment the wheels workflow reported success. Locking on workflow-success alone produced a 3-of-7 lock last release -- missing Windows, manylinux x86_64 and the sdist -- which nothing but the post-lock file count catches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
revarbat
added a commit
to BelfrySCAD/BelfrySCAD
that referenced
this pull request
Aug 21, 2026
* Viewers for object() and for geometry objects
Three related gaps, all reached from the same right-click menus.
**Geometry objects reach the mesh viewers.** A render() expression yields
an object() carrying the mesh as separate `vertices`/`faces` keys, so
_is_vnf -- which only ever matches a bare 2-list -- fired for obj.vnf but
never for obj itself. _geometry_object_vnf unwraps it. The 2D case is not a
straight unwrap: `paths` holds INDICES into `vertices`, so the contours are
resolved to real points first, which makes a region (a list of closed 2D
paths) -- what a 2D shape with holes actually is. Dispatch is on `dim`:
3 -> VNF viewer, 2 -> Region viewer, plus Path when there is exactly one
contour, following the existing "offer both readings and let the user pick"
convention.
Deliberately NOT folded into _is_vnf: that predicate also spots VNF
literals in source text, where an object is not a candidate at all.
**ObjectViewer.** Key/Type/Value, read-only by default -- the only
possibility for a debugger value, which has no source span to write back
to -- and editable from "Edit as Object..." with add/delete/rename, undo,
and a Save that rewrites the call. Values are shown and typed as OpenSCAD
source (`true`, `undef`, `[1,2]`), since that is what is being edited.
An object is a CALL, not a bracket literal, so it needed its own span
finder and text round-trip rather than riding on
_iter_enclosing_literals/ast.literal_eval.
**Editing is offered only for all-literal calls.** `object(other, [["b"]])`
depends on `other`, whose contents are unknowable from source text, so
rewriting it would silently destroy the reference. _parse_object_call_args
returns None for those and they stay view-only.
Two bugs found while building this, both pinned by tests:
- Translating OpenSCAD's true/false/undef to Python cannot be a str.replace:
it turns the string "undefined" into "Noneined". The translation skips
quoted regions.
- A duplicate key overwrites IN PLACE and keeps its original position --
object(a=42, b=1, a=99) is { a = 99; b = 1; }, checked against the
reference binary. Remove-and-append would have been DELETE semantics, and
the difference is observable because an object is insertion-ordered.
And one the user caught: "View as Object" on a call referencing a variable
opened an EMPTY viewer, because the lexical menu offered a source-text view
it could not populate. That view is now gated on the call being resolvable,
with a second guard in _open_object_viewer so an empty window cannot appear
by another route. Such objects remain fully inspectable at runtime, where
the debugger hands over resolved values.
834 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Require openscad_cpp_evaluator 0.39.0; bump to 0.76.0
Brings polyhedron(vnf) and object()'s single-element delete entry
(BelfrySCAD/openscad_cpp_evaluator#106), both of which this release's
viewers assume.
Locked only AFTER PyPI's index listed all 7 files, rather than the moment
the wheels workflow reported success. Locking on workflow-success alone
produced a 3-of-7 lock last release -- missing Windows, manylinux x86_64
and the sdist -- which nothing but the post-lock file count catches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent fixes, both reported while using
render()expressions in anger.object(): a single-element entry deletes that keyAn unnamed list argument holds entries:
[key, value]sets, and the single-element[key]deletes. Only the two-element form was implemented, so any script using a delete gotundeffor the whole call — silently, with no diagnostic pointing at why.Checking the rest of the semantics against OpenSCAD 2026.02.01 turned up more than the reported gap:
[["b"]]deleteundef[["zz"]]delete a missing keyundefundef, silentundef+ warningDeletion removes the key rather than blanking it, which is observable because
ValueObjectis insertion-ordered andoscEqualis order-sensitive:object(a, [["b"], ["b", 99]])putsblast, whileobject(a, [["b", 99], ["b"]])has nobat all. Both are pinned.All 19 cases — values and warning text — were diffed character-for-character against the reference binary with
--enable=object-function. The diff is empty. The warnings reproduce two of upstream's own inconsistencies: the "not a list" case puts spaces inside its parens where every other case does not, and the "unnamed argument" case ends with a trailing space. Those strings are what a user greps for, so matching them is the point.mergeObjectArgsgained anEvaluatorand a position so it can warn; both call sites — the interpreter'sbuiltinObjectand the VM'sOp::CallFn— pass them through.polyhedron()accepts a VNF 2-list[vertices, faces]is what every BOSL2 function passes around and whatobj.vnfholds. All four of these are now the same call:Only considered when
faceswas not supplied separately, so the two-argument form always wins and can never be reinterpreted. The discriminator is BOSL2's ownis_vnftest: a VNF's second element is a list of lists, where a points list that happens to have length 2 has a point there — bare numbers. Such a list is left alone and still errors as the invalid polyhedron it is.Verified both directions against real BOSL2:
polyhedron(spheroid(...))matches the volumevnf_polyhedron()produces, andvnf_polyhedron(obj.vnf)accepts ours.Testing
967 tests pass under both
OSCAD_BYTECODE_VM=0and=1.🤖 Generated with Claude Code