Skip to main content

Constrained Geometry

Constrained geometry lets you create lines, arcs, and circles that are tangent to existing sketch elements. Instead of calculating positions and angles by hand, you describe the relationship you want and FluidCAD solves the geometry for you.

The three constrained primitives are:

  • tLine() — a line tangent to one or two objects
  • tArc() — an arc tangent to objects, points, or the previous element
  • tCircle() — a circle tangent to two objects

Constraint qualifiers

When a constrained shape is tangent to a circle or curve, there are often multiple valid solutions. For example, a circle tangent to two other circles can sit between them, wrap around them, or fit inside them. Qualifiers let you specify which geometric relationship you want.

import { outside, enclosing, enclosed } from 'fluidcad/constraints';

Under the hood, these qualifiers correspond to the position of the solution relative to the "material side" of a curve — the interior of a circle (the filled region) or the left side of a line (relative to its direction). The three qualifiers describe three distinct spatial relationships:

outside(obj) — external tangency

The solution and obj are external to one another — they touch but do not overlap. For circles, this means the solution sits on the outside of obj, tangent at one point with no shared interior.

Use outside() when you want the solution to stay clear of the wrapped object — like a belt running around a pulley, or a tangent line that does not cross into the circle.

import { enclosing, outside } from "fluidcad/constraints";
import { circle, sketch } from "fluidcad/core";
import { tLine } from "fluidcad/core";

sketch("xy", () => {
const c1 = circle(100).guide()
const c2 = circle([200, 0], 40).guide()

tLine(outside(c1), outside(c2))
tLine(enclosing(c1), enclosing(c2))
})

Tangent lines with outside() qualifiers

enclosing(obj) — the solution wraps around the object

The solution encloses obj — the argument circle is inside the solution. The solution is the larger shape that contains obj.

Use enclosing() when you want the solution to wrap around the constraint object. For example, a tangent arc that sweeps around a guide circle from the outside.

import { sketch, circle, tArc } from 'fluidcad/core';
import { enclosing } from 'fluidcad/constraints';

sketch("xy", () => {
const c1 = circle(100).guide()
const c2 = circle([200, 0], 80).guide()

tArc(enclosing(c1), enclosing(c2), 200)
})

Tangent arc enclosing both guide circles

enclosed(obj) — the solution fits inside the object

The solution is enclosed by obj — the solution sits inside the argument circle. This is the inverse of enclosing(): the solution is the smaller shape contained by obj.

Use enclosed() when you need a solution that fits within an existing circle — for instance, an internal tangent circle nestled inside a larger construction circle.

import { sketch, circle, tCircle } from 'fluidcad/core';
import { enclosed } from 'fluidcad/constraints';

sketch("xy", () => {
const c1 = circle(300).guide()
const c2 = circle([60, 0], 240).guide()

tCircle(enclosed(c1), enclosed(c2), 100)
})

Tangent circles enclosed by both guide circles

How qualifiers reduce the number of solutions

Without any qualifier, FluidCAD returns all geometrically valid solutions. For a circle tangent to two other circles, there can be up to 8 solutions. Qualifiers act as filters — each one constrains the spatial relationship for that argument, which narrows the result set. Combining qualifiers on both arguments (e.g., outside(c1) and enclosing(c2)) narrows the results further, often to just one or two solutions.

Qualifier combinationMeaning
outside(c1), outside(c2)Solution is external to both circles
enclosing(c1), enclosing(c2)Solution wraps around both circles
enclosed(c1), enclosed(c2)Solution fits inside both circles
enclosing(c1), outside(c2)Solution wraps around c1 but stays external to c2
No qualifiersAll valid solutions are returned

All solutions (no qualifier)

With two guide circles and no qualifier, tLine() returns every valid tangent line:

import { circle, sketch } from "fluidcad/core";
import { tLine } from "fluidcad/core";

sketch("xy", () => {
const c1 = circle(100).guide()
const c2 = circle([200, 0], 40).guide()

tLine(c1, c2)
})

All tangent line solutions

The mustTouch option

tArc() and tCircle() accept an optional mustTouch boolean as their last argument. The solver generally treats input lines and arcs as infinite — extending them to find tangent solutions even when those solutions don't physically meet the finite segments you drew. Passing mustTouch: true filters out those phantom solutions:

// @screenshot waitForInput
import { sketch, aLine, vLine, move, tCircle } from 'fluidcad/core';

sketch("xy", () => {
const l1 = aLine(45, 200).guide()
move([-50, 0])
const l2 = vLine(200).guide()

// Without mustTouch, the solver returns every valid tangent circle,
// including ones that don't touch the finite line segments.
tCircle(l1, l2, 200)
})

Unfiltered tangent circles — some don't touch the segments

Without the filter, the solver returns every valid tangent circle, including ones that sit beyond the ends of the lines. Adding mustTouch: true keeps only the solutions in contact with both segments:

// @screenshot waitForInput
import { sketch, aLine, vLine, move, tCircle } from 'fluidcad/core';

sketch("xy", () => {
const l1 = aLine(45, 200).guide()
move([-50, 0])
const l2 = vLine(200).guide()

// mustTouch=true keeps only solutions that physically touch both segments
tCircle(l1, l2, 200, true)
})

Filtered tangent circle — only the touching solution remains

This is useful when the solver finds multiple valid tangent arcs or circles and you only want the ones in physical contact with both inputs.

tLine

Tangent continuation

The simplest use — draw a line that continues in the current tangent direction (shown by the orange arrow in the viewport):

sketch("xy", () => {
arc(50, 0, 90)
tLine(100) // 100 units in the tangent direction
})

See the Primitive Geometries guide for a screenshot of this form.

Tangent to two objects

Draw a line tangent to two circles, arcs, or curves. Without a qualifier the solver returns all valid tangent lines:

import { circle, sketch } from "fluidcad/core";
import { tLine } from "fluidcad/core";

sketch("xy", () => {
const c1 = circle(100).guide()
const c2 = circle([200, 0], 40).guide()

tLine(c1, c2)
})

All tangent line solutions

Different qualifiers select different tangent lines:

import { enclosing, outside } from "fluidcad/constraints";
import { circle, sketch } from "fluidcad/core";
import { tLine } from "fluidcad/core";

sketch("xy", () => {
const c1 = circle(100).guide()
const c2 = circle([200, 0], 40).guide()

tLine(outside(c1), outside(c2))
tLine(enclosing(c1), enclosing(c2))
})

Tangent lines with qualifiers — outside vs enclosing

Tangent between arcs

The same two-object form works on arcs:

import { sketch, arc, move, tLine } from 'fluidcad/core';

sketch("xz", () => {
move([-20, 0])
const a1 = arc(100, 0, 180)
move([50, -150])
const a2 = arc(50, 270, 0)

tLine(a1, a2) // line tangent to both arcs
})

Tangent lines between two arcs

Accessing tangent line endpoints

The result of a two-object tLine() exposes .start() and .end() vertices, which you can use to connect other geometry:

const t1 = tLine(outside(c1), outside(c2))
const t2 = tLine(enclosing(c1), enclosing(c2))

// Connect the tangent lines with arcs
tArc(t1.end(), t2.end(), t1.tangent())
move(t1.start())
tArc(t2.start(), t1.start(), t1.tangent().reverse())

tArc

tArc() is the most flexible constrained primitive. It can create arcs in several ways.

Tangent continuation

Continue from the current position and tangent direction (shown by the orange arrow in the viewport):

sketch("front", () => {
vLine(100)
tArc(50, 180) // radius 50, sweep 180°
tArc(80, -270) // radius 80, sweep -270° (clockwise)
})

Negative angles sweep clockwise, positive angles sweep counter-clockwise. See Primitive Geometries for the full set of continuation overloads (by radius/angle, to a point, with an explicit start tangent).

Tangent to two objects

Draw an arc tangent to two circles, lines, arcs, or points. Without qualifiers, both possible arcs are returned:

import { sketch, circle, tArc } from 'fluidcad/core';
import { outside } from 'fluidcad/constraints';

sketch("xy", () => {
const c1 = circle(160).guide()
const c2 = circle([200, 0], 60).guide()

tArc(outside(c1), outside(c2), 80) // radius 80, outside both
})

Tangent arcs between two circles

Between a circle and a line

Mixing object types works too — the solver finds every arc of the requested radius tangent to both:

import { sketch, aLine, circle, tArc } from 'fluidcad/core';

sketch("xy", () => {
const l = aLine(45, 150).guide()
const c = circle([100, 0], 40).guide()

tArc(c, l, 50) // radius 50, tangent to both
})

Tangent arcs between a line and a circle

Between two lines

A classic fillet — an arc of a given radius tangent to two intersecting lines:

import { sketch, aLine, vLine, move, tArc } from 'fluidcad/core';

sketch("xy", () => {
const l1 = aLine(45, 150).guide()
move([-50, 0])
const l2 = vLine(100).guide()

tArc(l1, l2, 50) // fillet-like arc between two lines
})

Fillet arcs between two lines

The solver returns the four fillet positions, one for each corner of the implied line intersection. Use qualifiers or mustTouch to pick the one you want.

Through two points

Two points and a radius define two possible arcs — the short one and the long one:

import { sketch, tArc } from 'fluidcad/core';

sketch("xy", () => {
tArc([-50, 0], [50, 0], 150) // arc through two points, radius 150
})

Arcs through two points with a given radius

From object to point

tArc() accepts a mix of points and objects, so you can build an arc from a circle to a fixed point:

import { sketch, circle, move, tArc } from 'fluidcad/core';
import { outside } from 'fluidcad/constraints';

sketch("xy", () => {
const c = circle([100, 0], 40).guide()
const p = [100, 50]
move(p)

tArc(outside(c), p, 100) // arc from circle to point, radius 100
})

Tangent arcs from a circle to a point

tCircle

tCircle() creates a full circle tangent to two objects. It takes the two objects and a diameter.

Tangent to two circles

Up to eight tangent circles exist for any pair of circles. Qualifiers filter that down to the spatial relationship you want:

import { sketch, circle, tCircle } from 'fluidcad/core';
import { outside, enclosing } from 'fluidcad/constraints';

sketch("xy", () => {
const c1 = circle(160).guide()
const c2 = circle([200, 0], 60).guide()

tCircle(c1, enclosing(c2), 160) // tangent to c1, enclosing c2
tCircle(outside(c1), outside(c2), 160) // outside both
})

Tangent circles with mixed qualifiers

Tangent to two lines

tCircle() will inscribe a circle of the requested diameter at every angular sector formed by the two lines. The mustTouch: true argument keeps only the inscribed circles that actually touch the finite segments:

import { sketch, aLine, vLine, move, tCircle } from 'fluidcad/core';

sketch("xy", () => {
const l1 = aLine(45, 300).guide()
move([-50, 0])
const l2 = vLine(300).guide()

tCircle(l1, l2, 200, true) // diameter 200, tangent to both lines
})

Tangent circle between two intersecting lines

Between a circle and a line

import { sketch, aLine, circle, tCircle } from 'fluidcad/core';

sketch("xy", () => {
const l = aLine(45, 150).guide()
const c = circle([100, 0], 60).guide()

tCircle(c, l, 100)
})

Tangent circles between a line and a circle

Through two points

The two-point form draws a circle of the requested diameter that passes through both points. Two solutions exist — one on each side of the chord:

import { sketch, tCircle } from 'fluidcad/core';

sketch("xy", () => {
tCircle([-50, 0], [50, 0], 300) // diameter 300, through both points
})

Tangent circles through two points