Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cylinder function #1451

Merged
merged 1 commit into from
Jun 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 81 additions & 7 deletions mathics/builtin/box/graphics3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,12 +694,85 @@ def get_boundbox_lines(self, xmin, xmax, ymin, ymax, zmin, zmax):
]


class Point3DBox(PointBox):
class Cylinder3DBox(_Graphics3DElement):
"""
Internal Python class used when Boxing a 'Cylinder' object.
"""

def init(self, graphics, style, item):
super(Cylinder3DBox, self).init(graphics, item, style)

self.edge_color, self.face_color = style.get_style(_Color, face_element=True)

if len(item.leaves) != 2:
raise BoxConstructError

points = item.leaves[0].to_python()
if not all(
len(point) == 3 and all(isinstance(p, numbers.Real) for p in point)
for point in points
):
raise BoxConstructError

self.points = [Coords3D(graphics, pos=point) for point in points]
self.radius = item.leaves[1].to_python()

def to_asy(self):
# l = self.style.get_line_width(face_element=True)

if self.face_color is None:
face_color = (1, 1, 1)
else:
face_color = self.face_color.to_js()

rgb = f"rgb({face_color[0]}, {face_color[1]}, {face_color[2]})"
return "".join(
f"draw(surface(cylinder({tuple(coord.pos()[0])}, {self.radius}, {self.height})), {rgb});"
for coord in self.points
)

def to_json(self):
face_color = self.face_color
if face_color is not None:
face_color = face_color.to_js()
return [
{
"type": "cylinder",
"coords": [coords.pos() for coords in self.points],
"radius": self.radius,
"faceColor": face_color,
}
]

def extent(self):
result = []
# FIXME: instead of `coords.add(±self.radius, ±self.radius, ±self.radius)` we should do:
# coords.add(transformation_vector.x * ±self.radius, transformation_vector.y * ±self.radius, transformation_vector.z * ±self.radius)
result.extend(
[
coords.add(self.radius, self.radius, self.radius).pos()[0]
for coords in self.points
]
)
result.extend(
[
coords.add(-self.radius, -self.radius, -self.radius).pos()[0]
for coords in self.points
]
)
return result

def _apply_boxscaling(self, boxscale):
# TODO
pass


class Line3DBox(LineBox):
def init(self, *args, **kwargs):
super(Point3DBox, self).init(*args, **kwargs)
super(Line3DBox, self).init(*args, **kwargs)

def process_option(self, name, value):
super(Point3DBox, self).process_option(name, value)
super(Line3DBox, self).process_option(name, value)

def extent(self):
result = []
Expand All @@ -715,12 +788,12 @@ def _apply_boxscaling(self, boxscale):
coords.scale(boxscale)


class Line3DBox(LineBox):
class Point3DBox(PointBox):
def init(self, *args, **kwargs):
super(Line3DBox, self).init(*args, **kwargs)
super(Point3DBox, self).init(*args, **kwargs)

def process_option(self, name, value):
super(Line3DBox, self).process_option(name, value)
super(Point3DBox, self).process_option(name, value)

def extent(self):
result = []
Expand Down Expand Up @@ -805,9 +878,10 @@ def _apply_boxscaling(self, boxscale):
# FIXME: GLOBALS3D is a horrible name.
GLOBALS3D.update(
{
"System`Polygon3DBox": Polygon3DBox,
"System`Cylinder3DBox": Cylinder3DBox,
"System`Line3DBox": Line3DBox,
"System`Point3DBox": Point3DBox,
"System`Polygon3DBox": Polygon3DBox,
"System`Sphere3DBox": Sphere3DBox,
}
)
37 changes: 37 additions & 0 deletions mathics/builtin/drawing/graphics3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,43 @@ def apply_min(self, xmin, ymin, zmin, evaluation):
return self.apply_full(xmin, ymin, zmin, xmax, ymax, zmax, evaluation)


class Cylinder(Builtin):
"""
<dl>
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]'
<dd>represents a cylinder of radius 1.
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}, $r$]'
<dd>is a cylinder of radius $r$ starting at ($x1$, $y1$, $z1$) and ending at ($x2$, $y2$, $z2$).
<dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}, ... }, $r$]'
<dd>is a collection cylinders of radius $r$
</dl>

>> Graphics3D[Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]]
= -Graphics3D-

>> Graphics3D[{Yellow, Cylinder[{{-1, 0, 0}, {1, 0, 0}, {0, 0, Sqrt[3]}, {1, 1, Sqrt[3]}}, 1]}]
= -Graphics3D-
"""

rules = {
"Cylinder[]": "Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]",
"Cylinder[positions_]": "Cylinder[positions, 1]",
}

messages = {
"oddn": "The number of points must be even."
}

def apply_check(self, positions, radius, evaluation):
"Cylinder[positions_, radius_?NumericQ]"

if len(positions.get_leaves()) % 2 == 1:
# number of points is odd so abort
evaluation.error("Cylinder", "oddn", positions)

return Expression("Cylinder", positions, radius)


class _Graphics3DElement(InstanceableBuiltin):
def init(self, graphics, item=None, style=None):
if item is not None and not item.has_form(self.get_name(), None):
Expand Down
15 changes: 8 additions & 7 deletions mathics/builtin/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1361,20 +1361,21 @@ class Large(Builtin):

element_heads = frozenset(
system_symbols(
"Rectangle",
"Disk",
"Line",
"Arrow",
"FilledCurve",
"BezierCurve",
"Point",
"Circle",
"Cylinder",
"Disk",
"FilledCurve",
"Inset",
"Line",
"Point",
"Polygon",
"Rectangle",
"RegularPolygon",
"Inset",
"Text",
"Sphere",
"Style",
"Text",
)
)

Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ def rec(cur, rest):


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

def rec(cur, rest):
if len(rest) > 1:
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_file_time(file) -> float:


def valuesname(name) -> str:
" 'NValues' -> 'n' "
"'NValues' -> 'n'"

assert name.startswith("System`"), name
if name == "System`Messages":
Expand Down
8 changes: 4 additions & 4 deletions mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def get_atoms(self, include_heads=True):
return []

def get_name(self):
" Returns symbol's name if Symbol instance "
"Returns symbol's name if Symbol instance"

return ""

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

def get_lookup_name(self):
" Returns symbol name of leftmost head "
"Returns symbol name of leftmost head"

return self.get_name()

Expand Down Expand Up @@ -1667,7 +1667,7 @@ def default_format(self, evaluation, form) -> str:
)

def sort(self, pattern=False):
" Sort the leaves according to internal ordering. "
"Sort the leaves according to internal ordering."
leaves = list(self._leaves)
if pattern:
leaves.sort(key=lambda e: e.get_sort_key(pattern_sort=True))
Expand Down Expand Up @@ -2048,7 +2048,7 @@ def get_sort_key(self, pattern_sort=False):
]

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

Expand Down
4 changes: 2 additions & 2 deletions mathics/core/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ class StreamsManager(object):

@staticmethod
def get_instance():
""" Static access method. """
"""Static access method."""
if StreamsManager.__instance == None:
StreamsManager()
return StreamsManager.__instance

def __init__(self):
""" Virtually private constructor. """
"""Virtually private constructor."""
if StreamsManager.__instance != None:
raise Exception("this class is a singleton!")
else:
Expand Down
35 changes: 34 additions & 1 deletion mathics/format/asy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from mathics.builtin.box.graphics3d import (
Graphics3DElements,
Cylinder3DBox,
Line3DBox,
Point3DBox,
Polygon3DBox,
Expand Down Expand Up @@ -151,6 +152,38 @@ def bezier_curve_box(self, **options) -> str:
add_conversion_fn(BezierCurveBox, bezier_curve_box)


def cylinder3dbox(self, **options) -> str:
if self.face_color is None:
face_color = (1, 1, 1)
else:
face_color = self.face_color.to_js()

asy = ""
i = 0
while i < len(self.points) / 2:
asy += "draw(surface(cylinder({0}, {1}, {2}, {3})), rgb({2},{3},{4}));".format(
tuple(self.points[i * 2].pos()[0]),
self.radius,

# distance between start and end
(
(self.points[i * 2][0][0] - self.points[i * 2 + 1][0][0])**2 +
(self.points[i * 2][0][1] - self.points[i * 2 + 1][0][1])**2 +
(self.points[i * 2][0][2] - self.points[i * 2 + 1][0][2])**2
) ** 0.5,

(1, 1, 0), # FIXME: currently always drawing around the axis X+Y
*face_color[:3]
)

i += 1

return asy


add_conversion_fn(Cylinder3DBox)


def filled_curve_box(self, **options) -> str:
line_width = self.style.get_line_width(face_element=False)
pen = asy_create_pens(edge_color=self.edge_color, stroke_width=line_width)
Expand Down Expand Up @@ -310,7 +343,7 @@ def polygon3dbox(self, **options) -> str:
add_conversion_fn(Polygon3DBox)


def polygonbox(self, **options):
def polygonbox(self, **options) -> str:
TiagoCavalcante marked this conversation as resolved.
Show resolved Hide resolved
line_width = self.style.get_line_width(face_element=True)
if self.vertex_colors is None:
face_color = self.face_color
Expand Down
18 changes: 18 additions & 0 deletions mathics/format/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
)

from mathics.builtin.box.graphics3d import (
Cylinder3DBox,
Line3DBox,
Point3DBox,
Polygon3DBox,
Expand Down Expand Up @@ -39,6 +40,23 @@ def graphics_3D_elements(self, **options):
add_conversion_fn(Graphics3DElements, graphics_3D_elements)


def cylinder_3d_box(self):
face_color = self.face_color
if face_color is not None:
face_color = face_color.to_js()
return [
{
"type": "cylinder",
"coords": [coords.pos() for coords in self.points],
"radius": self.radius,
"faceColor": face_color,
}
]


add_conversion_fn(Cylinder3DBox, cylinder_3d_box)


def line_3d_box(self):
# TODO: account for line widths and style
data = []
Expand Down
Loading