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

@twinfinity/scene-visuals

v6.1.2-ci.31615-beta

Published

Managed GreasedLine and 3D visuals

Readme

@twinfinity/scene-visuals

Babylon.js drawing primitives shared by Twinfinity apps: managed GreasedLine meshes, hover highlight, snap-preview symbols, text labels, and renderers for section geometry and plane sections. App-agnostic — it knows Babylon and @twinfinity/geometry, not the BIM model. @twinfinity/interaction builds its visual feedback on the first three.

Positions are Vec3 ([x, y, z]) from @twinfinity/geometry; line inputs are flat coordinate rows ([x1, y1, z1, x2, y2, z2, …], one row per polyline), which is what GreasedLine consumes.

ManagedLine

The one way to create, update and dispose a GreasedLine here — it owns the mesh lifecycle so nothing leaks.

import { ManagedLine } from '@twinfinity/scene-visuals';

const line = new ManagedLine(scene, {
  name: 'distance-1',
  color: Color3.Red(),
  width: 4, // default 5
  sizeAttenuation: true, // default true: width shrinks with distance; false = width in scene units
  renderingGroupId: 0,
  isPickable: false, // default false
  dashCount: 10,
  dashRatio: 0.5 // omit both for a solid line
});

line.setSegments([
  [0, 0, 0, 1, 0, 0],
  [1, 0, 0, 1, 1, 0]
]); // creates the mesh lazily on first call
line.setStyle(Color3.Blue(), 6);
line.setRenderingGroupId(2); // render on top
line.hide();
line.show();
line.dispose();

setSegments replaces all polylines; call it again to move the line rather than rebuilding it.

HighlightManager

One reused ManagedLine for "the thing under the pointer". highlightSegments(rows, options?) replaces the current highlight; clear() hides it; updateConfig changes the defaults; setRenderGroupId moves it between groups; dispose frees the mesh. color, width and renderingGroupId in options apply to that call only; the next call without them is back on the defaults and on the group setRenderGroupId last set.

const highlight = new HighlightManager(scene, { defaultColor: Color3.Yellow(), defaultWidth: 3 });
highlight.highlightSegments([[0, 0, 0, 1, 0, 0]], { color: Color3.Green(), renderingGroupId: 2 });
highlight.clear();

Snap previews

SnapPreview draws one screen-sized symbol at a world position: SnapPreviewStyle.Circle, Triangle, Square, Angle90, T or X. It always renders in group 2 (SNAP_PREVIEW_RENDERING_GROUP_ID) unless renderingGroupId says otherwise, so it is never hidden by geometry.

SnapPreviewManager is what you normally use: showSnap(position, options) moves the existing symbol when only the position changed and rebuilds it only when the style, colour, size, thickness or rendering group changes; showSnap(undefined, …) and clear() remove it.

const previews = new SnapPreviewManager(scene);
previews.showSnap([1, 2, 3], {
  style: SnapPreviewStyle.Square,
  color: Color3.Red(),
  size: 20, // px, default 10
  thickness: 5, // px, default 5
  renderingGroupId: 2 // optional; default SNAP_PREVIEW_RENDERING_GROUP_ID
});
previews.clear();
previews.dispose();

The manager takes an optional factory (position, options, scene) => SnapPreviewLike as its second argument, so tests can run the reuse logic without creating a real preview mesh.

Text labels

import { createTextPlane, placeTextPlane } from '@twinfinity/scene-visuals';

const label = createTextPlane(scene, '2.350 m', {
  fontSizePx: 40, // default 40; multi-line text splits on \n
  marginPx: 10, // default 10
  fontFamily: 'Arial', // default 'Arial'
  textColor: '#ffffff', // default '#ffffff'
  backgroundColor: 'rgba(0,0,0,0.4)', // default 'rgba(0,0,0,0.4)'
  textureScale: 512, // texture px per world unit; default 512
  renderingGroupId: 2 // default 0
});
placeTextPlane(label, midpoint, [0, 0.1, 0]); // position + optional offset
label.dispose();

The plane is a full billboard (BILLBOARDMODE_ALL), unlit and double-sided, so it always faces the camera. computeOrientation (below) is for labels that should not billboard.

Section geometry

renderSectionGeometry(geometry, scene, options?) draws a computed section as one GreasedLine mesh per enabled category. There are nine categories, the keys of SectionRenderStyles, and all are enabled by default: cutLines, visibleLines, hiddenLines, silhouettesVisible, silhouettesHidden, featureEdges, stairArrows, outlineVisible and outlineHidden. To draw fewer, set enabled: false on each one you don't want. The result exposes only dispose(); the meshes are not handed back.

import { renderSectionGeometry } from '@twinfinity/scene-visuals';

const result = renderSectionGeometry(geometry, scene, {
  styles: {
    cutLines: { color: Color3.FromHexString('#dc2626'), width: 10 },
    visibleLines: { color: Color3.Black(), width: 5 },
    hiddenLines: { color: Color3.FromHexString('#6b7280'), width: 4, dashCount: 10, dashRatio: 0.5 },
    featureEdges: { enabled: false }
  },
  namePrefix: 'drawing-view-slab',
  renderingGroupId: 2,
  widthScale: 1
});

result.dispose();

SectionRenderOptions has exactly those four fields. Dash settings are flat on each SectionLineStyle (dashCount, dashRatio); there is no nested dashPattern, no isPickable, and no per-category mesh on the result. The usual update pattern is dispose-then-render:

let current: SectionRenderResult | null = null;
async function updateDrawing() {
  const geo = await section.compute(products);
  current?.dispose();
  current = renderSectionGeometry(geo, scene, options);
}

Plane sections

PlaneSectionVisual<TProduct> manages the visuals of a live plane section (PlaneSectionResult): per-product line buckets and optional fill meshes, with colour and material caches. setLineOptions / setFillOptions restyle, setLineRenderGroupId / setFillRenderGroupId move them between groups, getProductVisual(product) / forEachProductVisual reach the per-product meshes, and dispose() frees everything. Options: parent, name, plane, line (enabled, width, renderingGroupId, color, namePrefix), fill (enabled, renderingGroupId, color, opacity, offset, namePrefix) and productColorResolver. A color is a PlaneSectionColorOption: a fixed Color3, a (product) => Color3 | undefined resolver, or undefined to fall back to productColorResolver.

Colour and orientation helpers

  • sampleProductColorRgba(product) reads a representative colour and alpha from the product's mesh style metadata, not from Babylon materials: the first mesh whose style.surfaceColor is non-zero (RGBA, 0–255), else the first mesh's surface RGB with alpha 1; sampleProductColor is the RGB-only variant. Both are typical productColorResolver implementations. resolveColorOption(option, product) turns a PlaneSectionColorOption into a Color3 for one product.
  • computeOrientation(inputs) chooses right/up/forward axes for a non-billboarded 3D label so it reads upright from the current camera. Inputs are the label's base axes, a baseline up, the camera vectors, and the previous OrientationState; the ORIENTATION_CONFIG hysteresis keeps the label from flipping while the camera hovers near a threshold.