Fornjot

early-stage b-rep CAD kernel, written in Rust

Fornjot 0.48.0

Shutting down the weekly release schedule was necessary. But waiting 6 months for putting out another release? Not quite as I thought it would go 😄

Time passed quickly, as it usually does, and now the year is basically over. But don't worry, that time was filled with lots of work on Fornjot. Let's take a look at some of the highlights!

Build/update operations and ObjectSet

Fornjot has APIs, called operations, to create and modify shapes. Two of the most important types of operations are build operations, which build shapes, and update operations, for low-level modifications of shapes.

These operations have been expanded and refined since the last release. Here's an example:

Sketch::empty()
    .add_region(
        Region::polygon(
            [
                [-x / 2., -y / 2.],
                [x / 2., -y / 2.],
                [x / 2., y / 2.],
                [-x / 2., y / 2.],
            ],
            services,
        )
        .insert(services),
    )

This example combines both build operations, like Sketch::empty and Region::polygon, with update operations, like add_region, to create a rectangular sketch.

There's also the new ObjectSet data structure, which is used by all objects that reference multiple other objects of the same type. Combining it with update operations makes it easier to select the specific objects you want to modify:

solid
    .update_shell(solid.shells().only(), |shell| {
        shell
            .update_face(shell.faces().first(), |face| {
                // Update the face here!
                todo!()
            })
            .insert(services)
    })

Here, we express that we want to update the only shell of solid (which will helpfully panic, if it has multiple shells, to tell us about our wrong assumption), and the first face of shell.

This is still a very basic way to select objects, and it becomes very tedious, or even impossible, to use in non-trivial scenarios. This is an area where further improvement is required.

Split and sweep

Sweeping is an operation that "sweeps" a 2D shape through space, to create a 3D shape (you can also sweep a 1D shape into a 2D shape, but that's more of an implementation detail). We've had this feature for a long time!

In this release, the sweep code has been cleaned up significantly, and this cleanup has enabled an important new capability: Where previously, the main use case was to sweep a sketch into a new shell, you can now take an existing shell and sweep one of its faces to extend the shell.

Combined with the new split operation, which can split a face into two, this can create interesting shapes that haven't been possible before. Here's an example:

cube
    .update_shell(cube.shells().only(), |shell| {
        let face = shell.faces().first();
        let cycle = face.region().exterior();

        let line = [
            (cycle.half_edges().nth(0).unwrap(), [split_pos]),
            (cycle.half_edges().nth(2).unwrap(), [split_pos]),
        ];

        let (shell, [face, _]) = shell.split_face(face, line, services);

        shell
            .sweep_face_of_shell(face, [0., 0., -size / 2.], services)
            .insert(services)
    })

And here's the result: A cube, which had one of its faces split, then one of the resulting smaller faces swept.

This is a big step forward in capability, but there's also much left to do:

Splitting faces is only a first step, a proof of concept. Right now, it enables models that haven't been possible before, and it paves the way for more advanced operations to be implemented in the future.

Holes

Cleaning up the sweep operations enabled another new feature: You can now create holes!

A cuboid with two holes: a blind hole on the left, and a through hole on the right

Here's an example that creates a blind hole in the bottom face of a shell:

shell.add_blind_hole(
    HoleLocation {
        face: bottom_face,
        position: [-offset, Scalar::ZERO].into(),
    },
    radius,
    [Scalar::ZERO, Scalar::ZERO, depth],
    services,
)

And this one creates a through hole, from a shell's bottom face to its top face:

shell
    .add_through_hole(
        [
            HoleLocation {
                face: bottom_face,
                position: [offset, Scalar::ZERO].into(),
            },
            HoleLocation {
                face: top_face,
                position: [offset, Scalar::ZERO].into(),
            },
        ],
        radius,
        services,
    )

While this is pretty neat, it is still quite limited:

Again, like the face splitting, this is just a first step. Future iterations will be more powerful, robust, and flexible.

And much more!

This is only the tip of the iceberg! There are many more improvements. Some user-visible, but smaller than the ones presented above. Others under the hood, where they support the user-visible features.

Check out the list below, for a more complete overview.

What's next?

I've been focused on new features for a while, and this release is the culmination of that. But now it's time to turn inward. To lift some of the limitations of those features, and to add new and better features, we need better infrastructure.

The planning process for this has started. We'll have to see where it takes us, but #2116, #2117, and #2118, are probably what will keep me busy for a while.

However, this is not all there is to do! There are open issues and a feature wishlist with many more work items, and help is always appreciated. So if you see anything there that appeals to you, or have an idea of your own, please feel free to jump in and help out!

Sponsors

Fornjot is supported by @MitchellHansen, @webtrax-oz, @seanjensengrey, @reivilibre, @lthiery, @ahdinosaur, @martindederer, @bollian, and my other awesome sponsors. Thank you!

Additional thanks go to @jonnedelm, @refarb, and @Retraze, who also supported this release with their financial contribution!

If you want Fornjot to be sustainable long-term, please consider supporting me too.

Library improvements

Improvements to Fornjot libraries.

fj

fj-core

fj-viewer

Other changes

Improvements that are not associated with a specific Fornjot library.