This notebook goes through how to draw a candy cane by bending a segment.
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from spomso.cores.helper_functions import generate_grid, smarter_reshape
from spomso.cores.post_processing import hard_binarization
from spomso.cores.geom_3d import Line
# size of the volume
co_size = 3, 3, 7
# resolution of the volume
co_resolution = 100, 100, 250
show = "FIELD" # BINARY, FIELD
show_midplane = True
show_3d = False
Point cloud of coordinates is created with the generate_grid function, which takes the size of the coordinate system and the number of points in each direction (co_resolution).
coor, co_res_new = generate_grid(co_size, co_resolution)
If there is an even number of points along any direction the function will automatically convert it to an odd number, therefore the origin is always included in the point cloud. The point cloud is a numpy.ndarray with a shape of (D, N), where D is the number of dimensions and N is the number of data points.
Create the line/segment with a length of 6.5:
seg = Line((-5, 0, 0), (1.5, 0, 0))
Give the line a thickness of 0.15:
seg.rounding(0.15)
<function spomso.cores.modifications.ModifyObject.rounding.<locals>.new_geo_object(co, *params)>
Bend the line around the origin with the bending radius of 0.5 and for an angle of π:
seg.bend(0.5, np.pi)
<function spomso.cores.modifications.ModifyObject.bend.<locals>.new_geo_object(co, *params)>
Rotate and position the candy cane in space:
seg.rotate(-np.pi/2, (1, 0, 0))
seg.rotate(-np.pi/2, (0, 0, 1))
seg.move((0, 0, 2.5))
Evaluate the SDF of the candy cane to create a signed distance field 3D map. This is done by calling the create method and passing in the point cloud of coordinates:
candy_cane_pattern = seg.create(coor)
Convert the distance field to a binary voxel map, where 1 corresponds to the interior and 0 to the exterior of the geometry.
if show_midplane:
field = smarter_reshape(candy_cane_pattern, co_resolution)
if show=="BINARY":
pattern_2d = hard_binarization(field, 0)
if show=="BINARY":
pattern = hard_binarization(candy_cane_pattern, 0)
print("Drawing results...")
# Mid-plane cross-section plot
if show_midplane and show=="BINARY":
fig, ax = plt.subplots(1,1, figsize=(8.25, 8.25))
ax.imshow(pattern_2d[co_resolution[0]//2, :, :].T,
cmap="binary_r",
extent=(-co_size[0]/2, co_size[0]/2,
-co_size[2]/2, co_size[2]/2),
origin="lower"
)
ax.grid()
fig.tight_layout()
plt.show()
if show_midplane and show == "FIELD":
fig, ax = plt.subplots(1, 1, figsize=(8.25, 8.25))
print(field.shape)
ax.imshow(field[co_resolution[0]//2, :, :].T,
cmap="binary_r",
extent=(-co_size[0] / 2, co_size[0] / 2,
-co_size[2] / 2, co_size[2] / 2),
origin="lower"
)
x_mask = coor[1] == 0
cs = ax.contour(coor[0, x_mask].reshape(co_res_new[0], co_res_new[2]),
coor[2, x_mask].reshape(co_res_new[0], co_res_new[2]),
field[co_resolution[0]//2, :, :],
cmap="plasma_r")
ax.clabel(cs, inline=True, fontsize=10)
ax.grid()
fig.tight_layout()
plt.show()
Drawing results... (101, 101, 251)
# Isosurfaces plot
if show_3d and show=="BINARY":
fig = go.Figure(data=go.Volume(
x=coor[0],
y=coor[1],
z=coor[2],
value=pattern,
isomin=0.1,
isomax=1,
opacity=0.1,
surface_count=2,
))
fig.show()
if show_3d and show=="FIELD":
fig = go.Figure(data=go.Volume(
x=coor[0],
y=coor[1],
z=coor[2],
value=candy_cane_pattern,
isomin=-0,
isomax=0.5,
opacity=0.1,
surface_count=5,
))
fig.show()