From 86c03db25be898b805d9b28dfcc01e97008409a7 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Fri, 16 Oct 2026 02:25:36 +0400 Subject: [PATCH] Sphere: do not rewrite Sphere[] and Sphere[p]; fill in defaults when drawing In WMA Sphere[c] is an inert symbolic primitive: it stays Sphere[c] and the default radius 1 is only used when it is rendered. Mathics3 rewrote Sphere[c] to Sphere[c, 1] (and Sphere[] to Sphere[{0, 0, 0}, 1]) with rules that cannot be cleared, so packages that use Sphere[n] as an inert head (e.g. Sphere[7] for S^7) break. SphereBox now accepts 0, 1 or 2 arguments and fills in the default center and radius itself. Co-Authored-By: Claude Opus 5.5 --- mathics/builtin/box/graphics3d.py | 14 ++++++++++---- mathics/builtin/drawing/graphics3d.py | 11 +++++++---- test/builtin/drawing/test_plot.py | 10 ++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index b362a94a4d..999c2a7663 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -13,7 +13,7 @@ PolygonBox, ) from mathics.builtin.colors.color_directives import Opacity, RGBColor, _ColorObject -from mathics.builtin.drawing.graphics3d import Graphics3D, Style3D +from mathics.builtin.drawing.graphics3d import Coords3D, Graphics3D, Style3D from mathics.builtin.drawing.graphics_internals import GLOBALS3D from mathics.core.exceptions import BoxExpressionError from mathics.core.symbols import Symbol @@ -284,11 +284,17 @@ def init(self, graphics, style, item): self.edge_opacity, self.face_opacity = style.get_style( Opacity, face_element=True ) - if len(item.elements) != 2: + # Sphere[] and Sphere[positions] stay unevaluated, as in WMA; + # the default center and radius are filled in here. + elements = item.elements + if len(elements) > 2: raise BoxExpressionError - self.points = expr_to_list_of_3d_points(item.elements[0]) - self.radius = item.elements[1].to_python() + if elements: + self.points = expr_to_list_of_3d_points(elements[0]) + else: + self.points = (Coords3D(pos=(0, 0, 0)),) + self.radius = elements[1].to_python() if len(elements) == 2 else 1 def extent(self): result = [] diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 73cf69f62f..5dc1acc415 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -355,13 +355,16 @@ class Sphere(Builtin): >> Graphics3D[{Yellow, Sphere[{{-1, 0, 0}, {1, 0, 0}, {0, 0, Sqrt[3.]}}, 1]}] = -Graphics3D- + + 'Sphere' is a symbolic representation: it does not evaluate, and the \ + default center and radius are only used when it is drawn: + >> {Sphere[], Sphere[{1, 2, 3}]} + = {Sphere[], Sphere[{1, 2, 3}]} + >> Graphics3D[Sphere[{1, 2, 3}]] + = -Graphics3D- """ summary_text = "represent a sphere" - rules = { - "Sphere[]": "Sphere[{0, 0, 0}, 1]", - "Sphere[positions_]": "Sphere[positions, 1]", - } class Tube(Builtin): diff --git a/test/builtin/drawing/test_plot.py b/test/builtin/drawing/test_plot.py index db7fced865..d6bc4a29f3 100644 --- a/test/builtin/drawing/test_plot.py +++ b/test/builtin/drawing/test_plot.py @@ -202,6 +202,16 @@ def test__listplot(): ), "Background 3D", ), + ("Sphere[]", None, "Sphere[]", "Sphere is inert"), + ("Sphere[{1, 2, 3}]", None, "Sphere[{1, 2, 3}]", "Sphere is inert"), + ("f[Sphere[n_]] := n; f[Sphere[7]]", None, "7", "Sphere[n] as a head"), + ( + "StringContainsQ[Graphics3D[Sphere[{1, 2, 3}]]//TeXForm//ToString, " + '"sphere((1, 2, 3), 1)"]', + None, + "True", + "default radius when drawn", + ), ( "Graphics3D[Point[Table[{Sin[t], Cos[t], 0}, {t, 0, 2. Pi, Pi / 15.}]]] //Chop//TeXForm//ToString", None,