I have Zentangle bracelet and Generalized zentangle bracelet on Thingiverse.
The patterns of these two works are from Symmetrical triangle generator.
How did I design these works? The bracelet is a cylinder, but patterns are 2D. You may draw lines on the surface of a cylinder directly, such as Random maze cylinder generator. But, the generator used only horizontal and vertical lines. Using this method is not too complex. The patterns, in the beginning, have a variety of directions, so drawing lines on a cylinder will require complex mathematics and implementation.
Circle again
Let's leave the above works alone temporarily and think from the other direction. How can you draw a character on a cylinder? The simplest way is projecting the character to a cylinder. For example.
radius = 10;
height = 20;
thickness = 1;
$fn = 24;
render() intersection() {
difference() {
cylinder(
r1 = radius + thickness,
r2 = radius + thickness,
h = height, center = true);
cylinder(
r1 = radius,
r2 = radius,
h = height, center = true);
}
rotate([90, 0, 0]) linear_extrude(radius + thickness)
text("A", size = height, valign = "center", halign = "center");
}
It works. The character looks like this.
But, this method cannot achieve the effect like this.
The cylindrical projection cannot cross over 180 degrees because you simply linear_extrude
the 2D text.
Recall what I mentioned in Circle, you can use triangles to make a circle. How about a cylinder? You just have to linear_extrude
a circle so that a cylinder can consist of triangles, too. You can line up the triangles and intersect with a horizontal model linearly extruded from a 2D object.
This operation will get several arcs, trapezoids. Next, wrap these arcs to form a circle.
You'll get a cylinder with the 2D drawing on its surface. Isn't it simpler than drawing lines on the cylinders?
Implementation
Before writing code, we have to determine the dimension of the 2D drawing. For convenience, we position the 2D drawing as below.
Now that, if we want to put a character on the cylinder, we should place the character as below.
linear_extrude(thickness)
translate([font_size / 2, font_size / 2, 0])
rotate(90)
text("A", size = font_size, valign = "center", halign = "center");
We have to define a module which creates triangles required by a circle. Every triangle is an isosceles triangle, and the legs' length is the radius of the circle.
We can use the built-in polygon
module to create triangles. If a circle is composed of fn
triangles, the base angle is a = 360 / fn
. If the apex is placed at the origin, the coordinates of the other two vertexes are [radius * cos(a / 2), radius * sin(a / 2)]
and [radius * cos(a / 2), -radius * sin(a / 2)]
. According to the above, we define a one_over_fn_for_circle
module below.
radius = 10;
$fn = 24;
module one_over_fn_for_circle(radius, fn) {
a = 360 / fn;
x = radius * cos(a / 2);
y = radius * sin(a / 2);
polygon(points=[[0, 0], [x, y],[x, -y]]);
}
one_over_fn_for_circle(radius, $fn);
Then, we make the character stand up.
And use triangles to intersect with the character.
The circumference of the circle should equal the length of the 2D object, so 2 * PI * radius = length
. That is radius = length / (2 * PI)
.
We may loop to create a required triangle, move them to the correct position, intersect with the character, move it back, and rotate an angle to form a circle. So, if you just want to wrap a character, you may implement it as follow.
tx = "A";
font_size = 20;
thickness = 1;
$fn = 24;
module one_over_fn_for_circle(radius, fn) {
a = 360 / fn;
x = radius * cos(a / 2);
y = radius * sin(a / 2);
polygon(points=[[0, 0], [x, y],[x, -y]]);
}
module text_to_cylinder(tx, font_size, thickness, fn) {
r = font_size / 6.28318;
a = 360 / fn;
y = r * sin(a / 2);
for(i = [0 : fn - 1]) {
// move it back and rotate to form a circle
rotate(a * i) translate([0, -(2 * y * i + y), 0])
intersection() {
// line up the triangle
translate([0, 2 * y * i + y, 0])
linear_extrude(font_size)
one_over_fn_for_circle(r, fn);
// make the character stand up
translate([r - thickness, 0, font_size])
rotate([0, 90, 0])
// the character
linear_extrude(thickness)
translate([font_size / 2, font_size / 2, 0])
rotate(90)
text(tx, size = font_size, valign = "center", halign = "center");
}
}
}
text_to_cylinder(tx, font_size, thickness, $fn);
This implementation creates the model below.
You can see that the model edge is jagged. You can improve this by increasing the $fn
value. A bigger $fn
will have more triangles, however, more intersection operations with the characters, so it takes more time to render. It is one disadvantage of this method.
The algorithm of this module is suitable for all 2D drawing. As you saw in The children module, you can use children
to represent the operated module so that we can refactor the above implementation to a generalized one.
tx = "A";
font_size = 20;
thickness = 1;
fn = 24;
module one_over_fn_for_circle(radius, fn) {
a = 360 / fn;
x = radius * cos(a / 2);
y = radius * sin(a / 2);
polygon(points=[[0, 0], [x, y],[x, -y]]);
}
module square_to_cylinder(length, width, square_thickness, fn) {
r = length / 6.28318;
a = 360 / fn;
y = r * sin(a / 2);
for(i = [0 : fn - 1]) {
// line up the triangle
rotate(a * i) translate([0, -(2 * y * i + y), 0])
intersection() {
// line up the triangle
translate([0, 2 * y * i + y, 0])
linear_extrude(width)
one_over_fn_for_circle(r, fn);
// make the character stand up
translate([r - square_thickness, 0, width])
rotate([0, 90, 0])
children(0);
}
}
}
square_to_cylinder(font_size, font_size, thickness, fn)
linear_extrude(thickness)
translate([font_size / 2, font_size / 2, 0])
rotate(90)
text(tx, size = font_size, valign = "center", halign = "center");
The square_to_cylinder
module is generalized. You can place a 2D drawing after it, and tell it the required arguments. It will automatically create a cylinder with the 2D drawing on it. If you want, the drawing can come from the picture read by the built-in surface
module, such as PNG to pen holder.