npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@toolpath/dfm

v0.4.0

Published

Toolpath DFM rules: the rule list, the rules and the part checker, and the feature panel

Readme

Toolpath DFM

@toolpath/dfm is the DFM (design for manufacturing) rule list: each rule written as the sentence it is — "Holes with L/D to top of part ≥ 8" — whose words are its fields, beside its colour and how many of a part's features break it. Under the list, Import, Export and Copy LLM prompt share a rule set as one JSON file.

It also draws a feature's reach chart: the walls beside the feature, how high they stand and how far out.

And it shows a feature as the Engine read it — the feature panel: the features a clicked face could mean, the rules the chosen one breaks, what was measured of it, its reach, and its datasheet as sent. Its pinch points, the widest tool standing where the feature is tightest, are placed here and drawn by @toolpath/viewer.

The rules and the checker that counts them ship too, with no React, under @toolpath/dfm/model.

Install

npm install @toolpath/dfm @toolpath/ui react react-dom

Add the styles after Tailwind and the @toolpath/ui theme:

@import 'tailwindcss';
@import '@toolpath/ui/theme.css';
@import '@toolpath/dfm/dfm.css';

Exports

| Entry point | What it is | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | @toolpath/dfm | RuleList, Swatch, the list's keyboard helpers, ReachChart, ReachDrawing, the feature panel (FeaturePanel, FeatureDetails, CandidateList, ComparisonTable, storageFolds), their types | | @toolpath/dfm/model | The rules, checkPart, the rule-set file, the feature panel's rows, pinch points — no React, no DOM | | @toolpath/dfm/dfm.css | Lets the app's Tailwind see the list's and chart's classes |

The rules are an input

The package ships no default rules and stores nothing — not the rules, nor which of the feature panel's sections are open. The app keeps the rules — in memory, localStorage, a server — and passes them in with the functions that change them:

import { RuleList, type DfmRules } from '@toolpath/dfm'
import { checkPart, dfmFeatures } from '@toolpath/dfm/model'

const rules: DfmRules = { rules, add, update, remove, replace }

// From a part analysis: the Engine's report, and each feature's datasheet.
const features = dfmFeatures(report.features)
const check = checkPart(features, { sheets, datasheets, regionAreas }, rules.rules)

<RuleList
  rules={rules}
  check={check} // null while the part is still being analysed
  features={features}
  sheets={sheets}
  units="mm"
  selected={selectedTag}
  selectedGroup={selectedGroup}
  onInspect={(tag, group) => inspect(tag, group)}
  onHover={(hovered) => highlight(hovered)}
/>

sheets is each feature's measurements, built with featureSheet from its datasheet; datasheets is the datasheets as the API sent them, both by lower-cased feature tag; regionAreas is each face's area by region index.

Put ruleListKeys on the panel that holds the list, so the arrow keys walk the rules from anywhere in it.

actions puts the app's own buttons at the end of the footer's row, after Import…, Export and Copy LLM prompt — a way back to the app's default rules, say, since the package has none of its own.

Reach chart

ReachChart draws a feature's reach curve: the section through the part at the feature's wall, to scale. The feature is on the left, in blue; the walls step up on the right, with their heights and their distances from the feature written on. Pointing at the walls reads the curve at that distance.

import { ReachChart, ReachDrawing } from '@toolpath/dfm'
import { featureProfile, readReachCurve } from '@toolpath/dfm/model'

const curve = readReachCurve(datasheet) // null where the feature has none
const feature = featureProfile(sheets[tag], featureType)

export const Reach = () =>
  curve ? <ReachChart curve={curve} units="mm" feature={feature} /> : null

ReachChart is as wide as its container. ReachDrawing is the same section with a caption and a note on how to read it, for a larger window of its own. Both stop drawing the curve half an inch past where the walls reach full height; ReachDrawing says so in its note.

This is not the clearance overlay in @toolpath/tool-drawing/clearance: that draws the walls beside a cutting tool, to show whether the tool fits. This draws the walls alone.

Feature panel

FeaturePanel is the card a click on the part opens: every feature the face belongs to, then the details of the one being read. With several faces shift-clicked, each face's candidates sit under a letter, above a table of what each was read as. It fills what it is put in — where it sits, and whether it can be dragged, is the app's — and the app decides what a click means: which faces, which candidates in which order, which is read.

import { FeatureDetails, FeaturePanel, storageFolds } from '@toolpath/dfm'
import { brokenRules, featureMeasurements, featureProfile } from '@toolpath/dfm/model'

const folds = storageFolds(localStorage, 'toolpath.fold.')

export const Inspector = () => (
  <FeaturePanel
    faces={[{ region, rows, selected }]}
    active={0}
    onSelect={(tag) => setSelected(tag)}
    onHover={setHovered}
    onClose={close}
    folds={folds}
  >
    <FeatureDetails
      feature={feature}
      units="mm"
      directionColor={directionHex}
      profile={featureProfile(sheets[key], feature.featureType)}
      required={check.required.has(key)}
      onFrame={() => frame(feature.tag)}
      rules={brokenRules(check, feature.tag, 'mm')}
      measurements={{
        status: 'ready',
        value: featureMeasurements({ features, feature, sheets, units: 'mm' }),
      }}
      record={{ status: 'ready', value: records[key] ?? null }}
      PopOut={AppWindow}
      folds={folds}
    />
  </FeaturePanel>
)

The parts work alone too. An app with an inspect panel of its own puts CandidateList and FeatureDetails in it and fills their slots with its own controls — a folder in a row's detail, a Pin button in its action, Zoom to in the details' actions, a folder badge in status.

  • Rules broken are BrokenRule rows: a colour, the rule's text, the figure that broke it, and optionally the limit and a note on hover. brokenRules builds them from this package's check; an app with a checker of its own builds them from its hits.
  • Measurements are featureMeasurements rows, each with how it was worked out behind an ⓘ. inchMark writes inches as 0.46".
  • Reach, the datasheet fields and the raw record read the feature's FeatureRecord: its report entry and datasheet as the API sent them.
  • Reads take time. measurements and record are Loadable. While loading, a section says what it is reading; on error it shows the app's message and a Retry, held until retryAt.
  • The look differs where apps want it to: look.rules, look.selected and look.muted. Left out, it is the CAD viewer's.
  • Folds are kept in the app's FoldStore; storageFolds(storage, prefix) builds one over a Storage. Without one, a section remembers only while it is mounted.
  • Pop-outs need PopOut, the app's window component: the panel decides what is open and fills it, and the app draws the frame. Define it at module level, and render it through a portal to the page's body: the panel's blur makes it the box a fixed window inside it would be placed in.
  • Keys: Escape closes the panel and the arrow keys walk the candidates. On a CandidateList used alone, put candidateListKeys on whatever holds it.

Pinch points

The widest tool a feature admits, standing where the feature is tightest. The data is here; the drawing is @toolpath/viewer's <ToolMarks>, which knows nothing of datasheets:

import { useMemo } from 'react'
import { pinchLabel, pinchMark, placePinchTool } from '@toolpath/dfm/model'
import { featureTriangles, ToolMarks, usePartContext } from '@toolpath/viewer'

// A child of <EnginePart> or <PartMesh>, which gives it the part.
export const PinchPoints = ({ datasheet, sheet, feature, units, shown }) => {
  const { model, geometry } = usePartContext()
  // Placing the tool searches the feature's faces: once per feature, not once per render.
  const marks = useMemo(() => {
    const mark = pinchMark(datasheet, sheet, feature.featureType)
    const triangles = featureTriangles(model, geometry, feature.tag)
    const tool = mark ? placePinchTool(mark, triangles, feature.machiningDirection) : null
    return tool && mark ? [{ ...tool, ...pinchLabel(mark, units) }] : []
  }, [datasheet, sheet, feature, model, geometry, units])
  return <ToolMarks marks={marks} visible={shown} />
}

placePinchTool finds the datasheet's tool frame against the feature's faces — the API gives its discs across the tool without saying which way x and y lie — and stands the tool on the tightest disc, turned toward the wall that pinches it. If the app moved the geometry to centre it, pass origin: where the CAD file's zero now sits.

Show and hide it from the viewer's toolbar — ViewerToolbar.ToolsButton on a tools control — with the state held by the app; start it shown. The tool is an end mill made from its diameter and corner; to draw it as the app's other tools are drawn, give the mark a profile from @toolpath/tool-drawing's outline of a flat or bull nose end mill that size.