Heart


I talked about creating a heart in Boolean operations and hull. Creating a heart is admittedly easy; however, you may consider more.

Heart

Is there any problem with the heart? No. But inconvenience will happen when it incorporates with other models. You probably have to move it to a particular position. Which base are you relying on when moving the heart?

You might have noticed. Some built-in modules in OpenSCAD have the center parameter. When it's true, the module is centered at (0, 0, 0). One corner of the module is at (0, 0, 0) if the center parameter is false.

While defining your fundamental modules, you may take the center parameter into consideration. The users of the modules, maybe yourself, will appreciate such flexibility. Providing such flexibility, however, needs more information about the geometry of the modules.

A centered heart

How to center a heart? Let's consider the bilateral symmetry of a heart first. You might remember that a heart is composed of a circle and a square.

radius = 10;

module heart(radius) {
    rotated_angle = 45;
    diameter = radius * 2;
    $fn = 48;

    rotate(-rotated_angle) union() {
        #circle(radius);
        translate([0, -radius, 0]) 
            square(diameter);
    }
}

heart(radius);

Heart

The radius of the circle is radius. The side length of the square is 2 * radius. The union of them is rotated 45 degrees. The first question is, how to position the under right angle on the y-axis?

Heart

As the figure shown above, you may move the module -radius * cos(45) along the x-axis. Next, invoke the same module again and mirror it. You'll get a heart.

radius = 10;

module heart_sub_component(radius) {
    rotated_angle = 45;
    diameter = radius * 2;
    $fn = 48;

    translate([-radius * cos(rotated_angle), 0, 0]) 
        rotate(-rotated_angle) union() {
            circle(radius);
            translate([0, -radius, 0]) 
                square(diameter);
        }
}

module heart(radius) {
    # heart_sub_component(radius);
    # mirror([1, 0, 0]) heart_sub_component(radius);
}

heart(radius);

The mirror transformation mirrors the modules on a plane through the origin. As the name shown, it's like a mirror in front of a module. If the mirror laid along the y-z plane and the orientation of the surface is toward the positive direction of the x-axis, the argument to mirror is the normal vector of [1, 0, 0].

As the code shown, I extract the same code and define a heart_sub_component module, so you don't have to repeat it while mirroring. It also increases the readability.

Heart

Is the heart centered? Not yet. The vertical center I want is the half of the length between the under right angle and the top of the circle. How long should I move?

Heart

As the figure shown, the height of the heart is (4 * radius * sin(45)) + (radius – radius * sin(45)). That is 3 * radius * sin(45) + radius after cleaning up the formula. Because the top of the circle is radius higher than the origin of the module, you only have to move (3 * radius * sin(45) + radius) / 2 - radius, which is 1.5 * radius * sin(45) - 0.5 * radius after cleaning up. After that, the heart is positioned at the center I want.

radius = 10;

module heart_sub_component(radius) {
    rotated_angle = 45;
    diameter = radius * 2;
    $fn = 48;

    translate([-radius * cos(rotated_angle), 0, 0]) 
        rotate(-rotated_angle) union() {
            circle(radius);
            translate([0, -radius, 0]) 
                square(diameter);
        }
}

module heart(radius) {
    center_offset_y = 1.5 * radius * sin(45) - 0.5 * radius;

    translate([0, center_offset_y, 0]) union() {
        heart_sub_component(radius);
        mirror([1, 0, 0]) heart_sub_component(radius);
    }
}

heart(radius);

Heart

A heart in the first quadrant

According to the above figure, if you want to position the heart in the first quadrant, move the heart radius + radius * cos(45) along the x-axis and 3 * radius * sin(45) along the y-axis. It aligns the heart with the bottom of the bounding box at the x-axis and the left of the bounding box at the y-axis.

height = 10;

module heart_sub_component(radius) {
    rotated_angle = 45;
    diameter = radius * 2;
    $fn = 48;

    translate([-radius * cos(rotated_angle), 0, 0]) 
        rotate(-rotated_angle) union() {
            circle(radius);
            translate([0, -radius, 0]) 
                square(diameter);
        }
}

module heart(radius, center = false) {
    offsetX = center ? 0 : radius + radius * cos(45);
    offsetY = center ? 1.5 * radius * sin(45) - 0.5 * radius : 3 * radius * sin(45);

    translate([offsetX, offsetY, 0]) union() {
        heart_sub_component(radius);
        mirror([1, 0, 0]) heart_sub_component(radius);
    }

}

heart(height / (3 * sin(45) + 1));

Heart

You can set default arguments for parameters while defining modules or functions. The center parameter of the above code defaults to false so heart(radius) will not be centered. If you want to center the heart, use heart(radius, center = true) or heart(radius, true). I prefer the former for clearness.

In the last example, I transfer height into radius because it's not intuitive for users to image that the heart is composed of two circles. The height or the width of the heart will be more convenient.