Sketch or QuickSketch

planex2d has two sketch types with the same drawing calls:

  • px.QuickSketch places every element once, from the pen and the references it is given, and keeps the geometry as drawn. No solver runs, so it stays fast for thousands of elements.

  • px.Sketch draws the same way and adds a constraint solver. Constraints, dimensions, expressions and Update keep the intent of the drawing when a value changes. With auto_constraint=True, the references of the drawing calls become constraints as well.

Both produce the same wires, faces and exports. A QuickSketch suits outlines that are drawn once, generated in a loop or imported. A px.Sketch suits parts whose dimensions change later, and geometry that has to be solved, such as a mechanism. One converts into the other (see Converting sketches). The pages of this section use the QuickSketch, and Constraint sketches adds the solver.

[1]:
import time
import numpy as np
import planex as px

What a QuickSketch does not have

A QuickSketch has no constraint calls. Constrain, Expression, Update, dof and the other analysis functions belong to px.Sketch. A constraint flag, or auto_constraint=True, raises an error that names the alternative. The references of the drawing calls work as in any sketch:

[5]:
qs = px.QuickSketch().Line(10, 0, id="a").Line(0, 5, id="b")
print("Constrain in a QuickSketch:", hasattr(qs, "Constrain"))
try:
    qs.Line(-10, 0, px.Horizontal)
except ValueError as err:
    print(err)
try:
    px.QuickSketch(auto_constraint=True)
except ValueError as err:
    print(err)
qs.Move((0, 5)).Line("b", parallel="a", id="c")
c = qs.Geometry("c")
print('Line("b", parallel="a"):', c["start"], "to", c["end"])
Constrain in a QuickSketch: False
px.Horizontal: a QuickSketch has no constraints; px.Sketch(...) has them
QuickSketch(auto_constraint=True): a QuickSketch has no constraints; px.Sketch(...) has them
Line("b", parallel="a"): (0.0, 5.0) to (10.0, 5.0)

px.Move((h, v)) starts a QuickSketch with its pen at a point. px.Sketch((h, v)) and px.QuickSketch((h, v)) do the same for the respective sketch type.

Speed: generated outlines

The example is a heat sink comb generated in a loop, with 50 fins of 30 on a base of 5 and four lines per fin. A QuickSketch draws it without solving. A px.Sketch with the flags px.Horizontal and px.Vertical on every line keeps each line’s direction as a constraint and solves as it draws, which takes time. Both give the same face. The times are measured on the machine that built this page.

[6]:
def comb(kind, n, flags=False):
    pitch, fin, width, base = 2.0, 30.0, 1.2, 5.0
    h, v = ((px.Horizontal,), (px.Vertical,)) if flags else ((), ())
    sk = kind((0, 0), dir=(1, 0))
    for _ in range(n):
        sk.Line(pitch - width, 0, *h).Line(0, fin, *v)
        sk.Line(width, 0, *h).Line(0, -fin, *v)
    sk.Line(0, -base, *v).Line(-n * pitch, 0, *h).Close(*v)
    return sk.Face(), n * pitch * base + n * width * fin


times = {}
runs = (("QuickSketch", px.QuickSketch, False), ("Sketch, flags", px.Sketch, True))
for name, kind, flags in runs:
    start = time.perf_counter()
    face, exact = comb(kind, 50, flags)
    times[name] = time.perf_counter() - start
    print(f"{name}: {times[name]:.4f} s, area {face.mass}, exact {exact}")
QuickSketch: 0.0010 s, area 2300.0000000000005, exact 2300.0
Sketch, flags: 0.0576 s, area 2300.0000000000005, exact 2300.0
A heat sink comb with 50 fins, 200 lines
Figure 2: A heat sink comb with 50 fins, 200 lines
QuickSketch 1.0 ms, Sketch, flags 57.6 ms
Figure 3: Drawing the comb and its face

Points from a file or a computation need no sketch at all: px.FromPolyline(points, closed=True) creates the wire directly. The example builds an outline of 20,000 measured points in three ways, with the times again measured on the build machine:

[8]:
a = np.linspace(0, 2 * np.pi, 20000, endpoint=False)
rho = 10 * (1 + 0.05 * np.sin(20 * a))
points = np.column_stack([rho * np.cos(a), rho * np.sin(a)])


def by_sketch(kind):
    sk = kind(points[0])
    for p in points[1:]:
        sk.Line(p)
    return sk.Close().Face()


ways = {"QuickSketch": lambda: by_sketch(px.QuickSketch),
        "Sketch": lambda: by_sketch(px.Sketch),
        "px.FromPolyline": lambda: px.Face([px.FromPolyline(points, closed=True)])}
for name, way in ways.items():
    start = time.perf_counter()
    face = way()
    print(f"{name}: {time.perf_counter() - start:.3f} s, area {face.mass}")
QuickSketch: 0.235 s, area 314.55195151496093
Sketch: 0.240 s, area 314.55195151496093
px.FromPolyline: 0.167 s, area 314.55195151496093