Skip to content

TPReal/tp-vector

Repository files navigation

Icon TPVector

TPVector is a library for generating vector geometry and graphics for a laser cutter. It offers a number of tools helpful for generating SVG files that can be imported into laser cutter software, like LightBurn or VisiCut, both for cutting and engraving.

Quick start

A collection of demo projects (defined here), where you can preview the files, as well as download SVG files suitable for the laser cutter software.

The library gives a high level of both compile-time safety, thanks to TypeScript, and runtime safety, thanks to immutability (see the Immutability document).

For the source code documentation, see Documentation, but note that it is neither ideal nor complete right now. It is recommended to consult the source code of the library - the type system will provide a lot of additional information.

Lines of code Lines of code (whichever counter happens to work better)

Features

Figures

Code
gather(
  figures.circle({radius: 3}),
  figures.rectangle({centered: true, side: 3, cornerRadius: 0.5}),
  figures.polygon([0, 0], [1, 1], [-1, 1], [-1, -1], [1, -1]),
  figures.circle({center: [4, 3], radius: 2}),
  figures.ellipse({center: [4, 3], radiusX: 1, radiusY: 2}),
)

Turtle

Code
Turtle.create()
  .forward(5).arcRight(120, 1).forward(1)
  .arcLeft(180, 1)
  .forward(1).arcLeft(120, 3).forward(5)

See the Turtle tutorial document for advanced uses.

Transform

Code
const myObject = gather(
  figures.rectangle({
    centered: true,
    width: 3,
    height: 2,
    cornerRadius: 0.2,
  }),
  figures.circle({
    center: [-1.3, -0.8],
    radius: 0.1,
  }),
);

const pieces = gather(
  myObject,
  myObject.scale(1.5).setAttributes({stroke: "red"}),
  myObject.translate(2, 1).setAttributes({stroke: "blue"}),
  myObject.rotateRight(30).setAttributes({stroke: "green"}),
  myObject.moveDown(1).skewTopToLeft(30).setAttributes({stroke: "orange"}),
);

Normalise

Code
const frame = viewBoxFromPartial({width: 3, height: 2});
const pieces = gather(
  figures.rectangle(frame),
  [
    figures.circle()
      .setAttributes({stroke: "red"}),
    figures.rectangle({width: 40, height: 10}).rotateRight(20)
      .setAttributes({stroke: "blue"}),
    figures.rectangle({width: 5, height: 4}).rotateLeft(15)
      .setAttributes({stroke: "green"}),
    Turtle.create()
      .right(10).forward(3).arcLeft(120, 1).forward(5)
      .arcLeft(120, 1).forward(3).closePath()
      .setAttributes({stroke: "orange"}),
  ].map(pc => pc
    .normalise({target: frame, align: "center"})
  ),
);

Layers
This is a single object with three layers: the outer circle is cut, the green circle is scored (cut, but not all the way through), and the inner shape is printed (engraved). See Layers and runs document for more information.

Code
Sheet.create({
  pieces: gather(
    // Cut this circle:
    figures.circle({radius: 3.4}),
    // Score this circle:
    figures.circle({radius: 3.2}).setLayer("score"),
    // Print this circle with the shape inside:
    gather(
      figures.circle({radius: 3}),
      Turtle.create()
        .curve(t => t.strafeLeft(4), {startSpeed: 4, targetSpeed: 3})
        .curve(t => t.strafeRight(4), {startSpeed: 3, targetSpeed: 4})
        .rotateRight().center()
        .setAttributes({fill: "white"}),
    ).setLayer("print"),
  ),
  // The list of runs and the layers they include.
  // In the comments, the default value of the `layers` parameter.
  runs: [
    {type: "cut", id: "score", /* layers: ["score"], */},
    {type: "print", /* layers: ["print"], */},
    {type: "cut", /* layers: [NO_LAYER, "cut"], */},
  ],
})

See demos/coins.ts for a more advanced example.

Text

Code
createText("TPVector", {
  font: "monospace",
  size: 5,
  fontAttributes: {bold: true},
  attributes: {
    letterSpacing: "-0.05em",
  },
}).moveUp(0.1).mirrorY()

Web fonts
The font is fetched from Google Fonts and embedded in the generated SVG. See Using external resources for more details.

Note: If the text using a web font is misaligned, refreshing the page should help.

Code
createText("TPVector", {
  font: await Font.googleFonts("Parisienne"),
  // Bonus: text path is used.
  textPath: {
    path: Turtle.create().turnBack().circle(3),
    align: "center",
    textAttributes: {
      // Improves letters joining.
      dominantBaseline: "central",
    },
  },
}).setLayer("print")

Images
The images are fetched from a URL and embedded in the generated SVG. See the Using external resources page for more details.

Code
const jsLogo = (await Image.fromURL(
  "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/JavaScript-logo.png/240px-JavaScript-logo.png"
));
const tsLogo = (await Image.fromURL(
  "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Typescript_logo_2020.svg/240px-Typescript_logo_2020.svg.png"
));
const pieces = gather(
  jsLogo.center().rotateRight(20),
  tsLogo.center().rotateLeft(20).moveRight(180),
);

Resources like fonts and images can be loaded from assets, i.e. local files placed alongside the code files.

Example code
const myImage = await Image.fromAsset(import(`./my_image.png`));
const myFont = await Font.fromAsset({
  name: "My Font",
  urlAsset: import(`./my_font.woff2`),
});

Clipping, gradients

Code
figures.circle()
  // Linear gradient for the fill.
  .useDefTool(createLinearGradient({
    stops: [
      {offset: 0, opacity: 0.6},
      {offset: 0.3, opacity: 0.6},
      {offset: 0.8, opacity: 0},
    ],
    from: [0, 0],
    to: [1, 1],
  }), "fill")
  // Radial gradient for the stroke
  .useDefTool(createRadialGradient({
    stops: [{offset: 0.8, opacity: 1}, {offset: 1, opacity: 0}],
  }), "stroke")
  .setAttributes({strokeWidth: 0.3})
  // Clip with a rectangle.
  .useDefTool(createClipPath(
    figures.rectangle({centered: true, width: 3, height: 1}).rotateLeft(10)
  ))
  .setLayer("print")

Mask

Code
const circles = figures.circle().translate(1, 1).mirrorXY();
const pieces = gather(
  circles
    .useDefTool(Mask
      .excl(figures.circle({
        center: [0.4, 0.4],
        radius: 1.2,
      }))
      .incl(figures.circle({
        center: [0.3, 0.3],
        radius: 0.8,
      }))),
  gather(
    circles
      .setAttributes({fillOpacity: 0.3}),
    circles
      .useDefTool(Mask
        .incl(
          figures.rectangle({
            centered: true,
            width: 1,
            height: 2.5,
            cornerRadius: 0.3,
          }).rotateRight(6),
          {fill: false, stroke: 0.3})),
  ).moveRight(4.1),
).setLayer("print");

Layout

Code
// Define some shapes.
const a = figures.circle({radius: 5});
const b = figures.rectangle({side: 10, centered: true}).rotateRight(25);
const c = Turtle.create()
  .forward(4).arcRight(120, 3)
  .forward(4).arcRight(120, 3)
  .forward(4).arcRight(120, 3)
  .center().rotateLeft(10);

const pieces = layouts.row({
  pieces: [

    // Block 1: Repeat figure `c` in a 3 by 3 grid.
    layouts.repeat({
      piece: c,
      rows: 3,
      columns: 3,
    }),

    // Block 2: Three rows of figures, collected in a column.
    // (Default gaps are 1.)
    layouts.pack([
      [a, b, c],
      [c, a, b],
      [b, c, a],
    ]),
    // Or equivalent:
    // layouts.column(
    //   layouts.row(a, b, c),
    //   layouts.row(c, a, b),
    //   layouts.row(b, c, a),
    // ),

    layouts.layout({
      // Iterate over the cells of a 3-dimensional cube.
      // Can have any number of dimensions.
      count: [3, 3, 3],
      // Calculate a piece for the cell of the cube, where
      // `(i, j, k)` is the index of the cell.
      pieceFunc: (i, j, k) =>
        // Select the shape based on `i`.
        [a, b, c][i].center()
          // Set opacity based on `j`.
          .setAttributes({opacity: 1 - 0.3 * j})
          // Scale based on `k`.
          .scale(1 - 0.3 * k)
          // Move it to the right position, based on `i` and `j`.
          .translate(i * 12, j * 12)
    }),

  ].map(pc => pc.center()),
  gap: 5,
});

Dual-sided
The green layer will is printed on the back of this square token. See the Dual-sided projects document for more information.

Code
Sheet.create({
  pieces: [
    figures.rectangle({centered: true, cornerRadius: 0.1}),
    createText("2", {font: "Times New Roman"}).center()
      .flipX().setLayer("print_back"),
    createText("2", {font: "Times New Roman"}).center()
      .setLayer("print"),
  ],
  runs: [
    {type: "print", id: "print_back", side: "back"},
    {type: "print"},
    {type: "cut"},
  ],
});

Interlock
The kerf correction is applied. The rightmost item uses also optional inner corners radius that reduce material stress to prevent breaking (useful for materials like acrylic).

Code
// Prepare tabs and slots functions with the specified options.
const {tabs, slots} = turtleInterlock({
  // Calibrated for the given laser and material.
  kerf: kerfUtil.millimeters(0.15, {millimetersPerUnit: 1}),
  materialThickness: 3,
  tabsDir: "left",
  // Slightly rounded corners, to make joining the pieces easier.
  outerCornersRadius: 0.8,
});

// Define the pattern of tabs.
const interlockPattern = TabsPattern.base(5).tab(5).base(10).tab(10);

const pieces = layouts.row(

  Turtle.create()
    .andThen(tabs, {
      pattern: interlockPattern.matchingTabs(),
      // Override the direction.
      options: {tabsDir: "right"},
    })
    .left().forward(10).left().forward(interlockPattern.length())
    .closePath(),

  Turtle.create()
    .andThen(tabs, interlockPattern)
    .right().forward(20).right()
    .andThen(tabs, interlockPattern.reverse())
    .closePath(),

  Turtle.create()
    .branch(slots, {
      pattern: interlockPattern.matchingSlots(),
      // Override some of the options.
      options: {innerCornersRadius: 0.5},
    })
    .right().forward(6).left().forward(interlockPattern.length())
    .left().forward(12).left().forward(interlockPattern.length())
    .closePath(),

);

See demos/tabs_and_slots.ts for a more advanced example.

Important reads

Demos

To view the demos start the Viewer (follow the steps from Installation and usage). The code can be found in demos_viewer.

Photos of some of the demo projects:


For a short summary of the coding style used in TPVector, see the Code style.

Copyright © 2023 by TPReal