Skip to content

Commit c8af25a

Browse files
authored
Merge pull request #1451 from TiagoCavalcanteTrindade/add-cylinder-function
Add Cylinder function
2 parents 499f1bf + 9895555 commit c8af25a

10 files changed

Lines changed: 213 additions & 33 deletions

File tree

mathics/builtin/box/graphics3d.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -694,12 +694,85 @@ def get_boundbox_lines(self, xmin, xmax, ymin, ymax, zmin, zmax):
694694
]
695695

696696

697-
class Point3DBox(PointBox):
697+
class Cylinder3DBox(_Graphics3DElement):
698+
"""
699+
Internal Python class used when Boxing a 'Cylinder' object.
700+
"""
701+
702+
def init(self, graphics, style, item):
703+
super(Cylinder3DBox, self).init(graphics, item, style)
704+
705+
self.edge_color, self.face_color = style.get_style(_Color, face_element=True)
706+
707+
if len(item.leaves) != 2:
708+
raise BoxConstructError
709+
710+
points = item.leaves[0].to_python()
711+
if not all(
712+
len(point) == 3 and all(isinstance(p, numbers.Real) for p in point)
713+
for point in points
714+
):
715+
raise BoxConstructError
716+
717+
self.points = [Coords3D(graphics, pos=point) for point in points]
718+
self.radius = item.leaves[1].to_python()
719+
720+
def to_asy(self):
721+
# l = self.style.get_line_width(face_element=True)
722+
723+
if self.face_color is None:
724+
face_color = (1, 1, 1)
725+
else:
726+
face_color = self.face_color.to_js()
727+
728+
rgb = f"rgb({face_color[0]}, {face_color[1]}, {face_color[2]})"
729+
return "".join(
730+
f"draw(surface(cylinder({tuple(coord.pos()[0])}, {self.radius}, {self.height})), {rgb});"
731+
for coord in self.points
732+
)
733+
734+
def to_json(self):
735+
face_color = self.face_color
736+
if face_color is not None:
737+
face_color = face_color.to_js()
738+
return [
739+
{
740+
"type": "cylinder",
741+
"coords": [coords.pos() for coords in self.points],
742+
"radius": self.radius,
743+
"faceColor": face_color,
744+
}
745+
]
746+
747+
def extent(self):
748+
result = []
749+
# FIXME: instead of `coords.add(±self.radius, ±self.radius, ±self.radius)` we should do:
750+
# coords.add(transformation_vector.x * ±self.radius, transformation_vector.y * ±self.radius, transformation_vector.z * ±self.radius)
751+
result.extend(
752+
[
753+
coords.add(self.radius, self.radius, self.radius).pos()[0]
754+
for coords in self.points
755+
]
756+
)
757+
result.extend(
758+
[
759+
coords.add(-self.radius, -self.radius, -self.radius).pos()[0]
760+
for coords in self.points
761+
]
762+
)
763+
return result
764+
765+
def _apply_boxscaling(self, boxscale):
766+
# TODO
767+
pass
768+
769+
770+
class Line3DBox(LineBox):
698771
def init(self, *args, **kwargs):
699-
super(Point3DBox, self).init(*args, **kwargs)
772+
super(Line3DBox, self).init(*args, **kwargs)
700773

701774
def process_option(self, name, value):
702-
super(Point3DBox, self).process_option(name, value)
775+
super(Line3DBox, self).process_option(name, value)
703776

704777
def extent(self):
705778
result = []
@@ -715,12 +788,12 @@ def _apply_boxscaling(self, boxscale):
715788
coords.scale(boxscale)
716789

717790

718-
class Line3DBox(LineBox):
791+
class Point3DBox(PointBox):
719792
def init(self, *args, **kwargs):
720-
super(Line3DBox, self).init(*args, **kwargs)
793+
super(Point3DBox, self).init(*args, **kwargs)
721794

722795
def process_option(self, name, value):
723-
super(Line3DBox, self).process_option(name, value)
796+
super(Point3DBox, self).process_option(name, value)
724797

725798
def extent(self):
726799
result = []
@@ -805,9 +878,10 @@ def _apply_boxscaling(self, boxscale):
805878
# FIXME: GLOBALS3D is a horrible name.
806879
GLOBALS3D.update(
807880
{
808-
"System`Polygon3DBox": Polygon3DBox,
881+
"System`Cylinder3DBox": Cylinder3DBox,
809882
"System`Line3DBox": Line3DBox,
810883
"System`Point3DBox": Point3DBox,
884+
"System`Polygon3DBox": Polygon3DBox,
811885
"System`Sphere3DBox": Sphere3DBox,
812886
}
813887
)

mathics/builtin/drawing/graphics3d.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,43 @@ def apply_min(self, xmin, ymin, zmin, evaluation):
335335
return self.apply_full(xmin, ymin, zmin, xmax, ymax, zmax, evaluation)
336336

337337

338+
class Cylinder(Builtin):
339+
"""
340+
<dl>
341+
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]'
342+
<dd>represents a cylinder of radius 1.
343+
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}, $r$]'
344+
<dd>is a cylinder of radius $r$ starting at ($x1$, $y1$, $z1$) and ending at ($x2$, $y2$, $z2$).
345+
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}, ... }, $r$]'
346+
<dd>is a collection cylinders of radius $r$
347+
</dl>
348+
349+
>> Graphics3D[Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]]
350+
= -Graphics3D-
351+
352+
>> Graphics3D[{Yellow, Cylinder[{{-1, 0, 0}, {1, 0, 0}, {0, 0, Sqrt[3]}, {1, 1, Sqrt[3]}}, 1]}]
353+
= -Graphics3D-
354+
"""
355+
356+
rules = {
357+
"Cylinder[]": "Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]",
358+
"Cylinder[positions_]": "Cylinder[positions, 1]",
359+
}
360+
361+
messages = {
362+
"oddn": "The number of points must be even."
363+
}
364+
365+
def apply_check(self, positions, radius, evaluation):
366+
"Cylinder[positions_, radius_?NumericQ]"
367+
368+
if len(positions.get_leaves()) % 2 == 1:
369+
# number of points is odd so abort
370+
evaluation.error("Cylinder", "oddn", positions)
371+
372+
return Expression("Cylinder", positions, radius)
373+
374+
338375
class _Graphics3DElement(InstanceableBuiltin):
339376
def init(self, graphics, item=None, style=None):
340377
if item is not None and not item.has_form(self.get_name(), None):

mathics/builtin/graphics.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,20 +1361,21 @@ class Large(Builtin):
13611361

13621362
element_heads = frozenset(
13631363
system_symbols(
1364-
"Rectangle",
1365-
"Disk",
1366-
"Line",
13671364
"Arrow",
1368-
"FilledCurve",
13691365
"BezierCurve",
1370-
"Point",
13711366
"Circle",
1367+
"Cylinder",
1368+
"Disk",
1369+
"FilledCurve",
1370+
"Inset",
1371+
"Line",
1372+
"Point",
13721373
"Polygon",
1374+
"Rectangle",
13731375
"RegularPolygon",
1374-
"Inset",
1375-
"Text",
13761376
"Sphere",
13771377
"Style",
1378+
"Text",
13781379
)
13791380
)
13801381

mathics/builtin/lists.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def rec(cur, rest):
477477

478478

479479
def set_part(varlist, indices, newval):
480-
" Simple part replacement. indices must be a list of python integers. "
480+
"Simple part replacement. indices must be a list of python integers."
481481

482482
def rec(cur, rest):
483483
if len(rest) > 1:

mathics/core/definitions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def get_file_time(file) -> float:
3232

3333

3434
def valuesname(name) -> str:
35-
" 'NValues' -> 'n' "
35+
"'NValues' -> 'n'"
3636

3737
assert name.startswith("System`"), name
3838
if name == "System`Messages":

mathics/core/expression.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def get_atoms(self, include_heads=True):
309309
return []
310310

311311
def get_name(self):
312-
" Returns symbol's name if Symbol instance "
312+
"Returns symbol's name if Symbol instance"
313313

314314
return ""
315315

@@ -320,7 +320,7 @@ def is_machine_precision(self) -> bool:
320320
return False
321321

322322
def get_lookup_name(self):
323-
" Returns symbol name of leftmost head "
323+
"Returns symbol name of leftmost head"
324324

325325
return self.get_name()
326326

@@ -1667,7 +1667,7 @@ def default_format(self, evaluation, form) -> str:
16671667
)
16681668

16691669
def sort(self, pattern=False):
1670-
" Sort the leaves according to internal ordering. "
1670+
"Sort the leaves according to internal ordering."
16711671
leaves = list(self._leaves)
16721672
if pattern:
16731673
leaves.sort(key=lambda e: e.get_sort_key(pattern_sort=True))
@@ -2048,7 +2048,7 @@ def get_sort_key(self, pattern_sort=False):
20482048
]
20492049

20502050
def equal2(self, rhs: Any) -> Optional[bool]:
2051-
"""Mathics two-argument Equal (==) """
2051+
"""Mathics two-argument Equal (==)"""
20522052
if self.sameQ(rhs):
20532053
return True
20542054

mathics/core/streams.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ class StreamsManager(object):
8989

9090
@staticmethod
9191
def get_instance():
92-
""" Static access method. """
92+
"""Static access method."""
9393
if StreamsManager.__instance == None:
9494
StreamsManager()
9595
return StreamsManager.__instance
9696

9797
def __init__(self):
98-
""" Virtually private constructor. """
98+
"""Virtually private constructor."""
9999
if StreamsManager.__instance != None:
100100
raise Exception("this class is a singleton!")
101101
else:

mathics/format/asy.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from mathics.builtin.box.graphics3d import (
2121
Graphics3DElements,
22+
Cylinder3DBox,
2223
Line3DBox,
2324
Point3DBox,
2425
Polygon3DBox,
@@ -151,6 +152,38 @@ def bezier_curve_box(self, **options) -> str:
151152
add_conversion_fn(BezierCurveBox, bezier_curve_box)
152153

153154

155+
def cylinder3dbox(self, **options) -> str:
156+
if self.face_color is None:
157+
face_color = (1, 1, 1)
158+
else:
159+
face_color = self.face_color.to_js()
160+
161+
asy = ""
162+
i = 0
163+
while i < len(self.points) / 2:
164+
asy += "draw(surface(cylinder({0}, {1}, {2}, {3})), rgb({2},{3},{4}));".format(
165+
tuple(self.points[i * 2].pos()[0]),
166+
self.radius,
167+
168+
# distance between start and end
169+
(
170+
(self.points[i * 2][0][0] - self.points[i * 2 + 1][0][0])**2 +
171+
(self.points[i * 2][0][1] - self.points[i * 2 + 1][0][1])**2 +
172+
(self.points[i * 2][0][2] - self.points[i * 2 + 1][0][2])**2
173+
) ** 0.5,
174+
175+
(1, 1, 0), # FIXME: currently always drawing around the axis X+Y
176+
*face_color[:3]
177+
)
178+
179+
i += 1
180+
181+
return asy
182+
183+
184+
add_conversion_fn(Cylinder3DBox)
185+
186+
154187
def filled_curve_box(self, **options) -> str:
155188
line_width = self.style.get_line_width(face_element=False)
156189
pen = asy_create_pens(edge_color=self.edge_color, stroke_width=line_width)
@@ -310,7 +343,7 @@ def polygon3dbox(self, **options) -> str:
310343
add_conversion_fn(Polygon3DBox)
311344

312345

313-
def polygonbox(self, **options):
346+
def polygonbox(self, **options) -> str:
314347
line_width = self.style.get_line_width(face_element=True)
315348
if self.vertex_colors is None:
316349
face_color = self.face_color

mathics/format/json.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111

1212
from mathics.builtin.box.graphics3d import (
13+
Cylinder3DBox,
1314
Line3DBox,
1415
Point3DBox,
1516
Polygon3DBox,
@@ -39,6 +40,23 @@ def graphics_3D_elements(self, **options):
3940
add_conversion_fn(Graphics3DElements, graphics_3D_elements)
4041

4142

43+
def cylinder_3d_box(self):
44+
face_color = self.face_color
45+
if face_color is not None:
46+
face_color = face_color.to_js()
47+
return [
48+
{
49+
"type": "cylinder",
50+
"coords": [coords.pos() for coords in self.points],
51+
"radius": self.radius,
52+
"faceColor": face_color,
53+
}
54+
]
55+
56+
57+
add_conversion_fn(Cylinder3DBox, cylinder_3d_box)
58+
59+
4260
def line_3d_box(self):
4361
# TODO: account for line widths and style
4462
data = []

0 commit comments

Comments
 (0)