mcpbeat

Zoning Envelope

alpacalabsllc/zoning-envelope

Generate interactive 3D zoning envelope viewers from zoning analysis reports. Use when the user asks to "visualize the zoning envelope" or see buildable massing in 3D, typically after /as:zoning-analysis-nyc. Requires a zoning analysis report as input.

3k tokens
context cost
the whole folder, loaded on every use
2
files
instructions only
0
copies elsewhere
how many repositories repackaged it
302
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/AlpacaLabsLLC/skills-for-architects --skill zoning-envelope

What comes with it

3 077 bytes besides the instruction
README.md

The instruction itself

21 sections, as written by the author

/as:zoning-envelope — 3D Zoning Envelope Viewer

Generate an interactive 3D axonometric zoning envelope viewer as a single HTML file. Uses Three.js with OrbitControls, loaded from a CDN — opens in any modern browser, no install or build step, but an internet connection is required the first time the file is viewed (the Three.js modules are fetched from jsdelivr, not embedded; offline the page loads but the 3D scene will not render).

Requires a zoning analysis report generated by /as:zoning-analysis-nyc. This skill is a renderer — it does not perform zoning calculations.

Project context

If PROJECT.md exists in the working directory, read it before fetching — lot geometry and bulk controls may already be on file from /as:zoning-analysis-nyc. After completing, offer a one-line envelope summary and viewer path to /as:project update for its Zoning section, each with a source and date. No PROJECT.md? Skip silently — or mention /as:project init if the user is clearly starting a project.

Usage

/as:zoning-envelope path/to/zoning-analysis.md
/as:zoning-envelope 250 hudson
/as:zoning-envelope

Step 1: Find the Report

If a .md path is provided

Read the file directly.

If a search term is provided (address, etc.)

Search for matching zoning analysis reports in the current working directory.

Use Glob + Grep to find reports matching the search term. If multiple matches, show the options and ask the user to pick one.

If no argument is provided

Search for the most recently modified zoning-analysis-*.md file in the current working directory. If found, confirm with the user. If not found, tell the user:

> No zoning analysis report found. Run /as:zoning-analysis-nyc first, then come back with /as:zoning-envelope.

If no Envelope Data block is found

If the report exists but lacks the ## Envelope Data JSON block (older report format), tell the user:

> This report was generated before the Envelope Data format was added. Re-run the zoning analysis to get an updated report, or I can attempt to parse the tables (results may be approximate).

Step 2: Parse Envelope Data

Read the ## Envelope Data JSON block from the report — a fenced code block containing:

{
  "lot_poly": [[x, y], ...],
  "unit": "ft",
  "setbacks": { "front": 6, "rear": 3, "lateral1": 3, "lateral2": 2 },
  "volumes": [
    { "type": "base", "inset": 20, "h_bottom": 0, "h_top": 85, "label": "base" },
    { "type": "tower", "inset": 10, "h_bottom": 85, "h_top": 290, "label": "tower" }
  ],
  "height_cap": 290,
  "info": { "title": "...", "zone": "...", "id": "...", "area": "..." },
  "stats": { "key": "value", ... },
  "scenarios": null
}

Step 3: Normalize to Envelope Model

From the parsed JSON, build the internal model:

  • LOT_POLY — the lot boundary polygon in local units
  • UNIT — "ft"
  • VOLUMES — array of volumes to extrude, each with inset distance, height range, label
  • HEIGHT_CAP — max height for the amber cap plane
  • INFO — title, zone, id, area for the overlay panels
  • STATS — key/value pairs for the parameters panel
  • SCENARIOS — if present, multi-scenario toggle data

Compute inset polygons using the insetPolygon(poly, distance) function. For multi-volume envelopes (base + tower), compute the tower inset from the base inset (cumulative), not from the lot polygon — so the tower is always smaller than the base.

Compute volumes by extruding inset polygons between height intervals.

Step 3: Generate HTML

Build a self-contained HTML file following the design system below.

Design System

| Element | Color | Opacity |

|---------|-------|---------|

| Background | #f5f3ef | 1.0 |

| Ground (lot) | #dcd7cd | 0.5 |

| Lot boundary | #2c2c2c | 1.0 |

| Setback zones | #c85a50 | 0.2 |

| Base volume faces | #6ba0c5 | 0.08–0.10 |

| Base volume edges | #6ba0c5 | 0.30–0.35 |

| Tower/upper volume | #6ba0c5 | 0.05 |

| Height cap / sky plane | #e8a849 | 0.10–0.15 |

| Labels | #333333 | 1.0 (canvas sprites) |

| Grid | #d0ccc4 | 0.15 |

Typography: Helvetica Neue, 11px for overlay panels, canvas sprites for 3D labels.

Layout:

  • Top-left: Title + address/zone
  • Top-right: Parameters panel (stats)
  • Bottom-left: Color legend
  • Bottom-right: Controls hint

All materials: transparent: true, depthWrite: false, side: DoubleSide.

CDN Import Map

<script type="importmap">
{ "imports": {
    "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
} }
</script>

Required Utility Functions

Include these in every generated HTML:

signedArea(poly) — Returns signed area. Positive = CCW winding.

insetPolygon(poly, d) — Shrinks polygon inward by distance d along edge-normal bisectors. Each vertex moves along the bisector of its two adjacent edge normals, with distance adjusted for the bisector angle. CRITICAL: The normal direction depends on polygon winding, which varies by data source (WGS84 vs EPSG:3857 produce opposite windings). The function MUST self-correct: after computing the inset, compare abs(signedArea(result)) against abs(signedArea(poly)). If the result is LARGER, negate the offset direction and recompute. This makes the function robust regardless of input winding.

triangulate(poly) — Ear-clipping triangulation for arbitrary simple polygons. Returns index array for BufferGeometry.setIndex().

extrudePolygon(poly, hBottom, hTop, color, opacity) — Returns a THREE.Group containing:

  • BufferGeometry with triangulated top/bottom faces + side quads
  • Wireframe edges: top ring, bottom ring, vertical edges at each vertex

groundPolygon(poly, color, opacity, yOffset) — Triangulated flat polygon at a given Y height.

centroid(poly) — Returns [cx, cz] for camera targeting and label placement.

createTextSprite(text, options) — Creates a THREE.Sprite with canvas-rendered text. Options: fontSize, color, bgColor.

Scene Setup

renderer = WebGLRenderer({ antialias: true })
renderer.setClearColor(0xf5f3ef, 1)
camera = PerspectiveCamera(35, aspect, 1, maxDim * 10)
camera.position = centroid + [maxDim * 1.5, maxDim * 1.0, maxDim * 1.5]
controls = OrbitControls with damping
AmbientLight(0xffffff, 0.75)
DirectionalLight(0xffffff, 0.35) from upper-right

Scale camera distance to the lot's maximum dimension so it works for both small townhouse lots (~25 ft) and large assembled sites (170 ft+).

Geometry Pipeline

For each generated file:

  • Fix polygon winding to CCW using signedArea
  • Lot ground plane: groundPolygon(lotPoly, ...) + outline + vertex markers (small spheres)
  • Edge labels: On edges > 20 units, place a createTextSprite at the edge midpoint, offset outward
  • Setback zone: Render lot polygon as red → overlay inset polygon as lot color. Draw inset as dashed line.
  • Volumes: For each volume in the model, compute inset if needed, then extrudePolygon(...)
  • Height cap: groundPolygon(topPoly, amber, ...) at max height
  • Height labels: Dashed vertical lines + text sprites at key heights
  • Street label: At the street-facing edge (Z ≈ 0 or the edge closest to the origin)
  • Area label: At lot centroid

10. Grid: THREE.GridHelper scaled to lot size, subtle opacity

Multi-Scenario Support

If SCENARIOS is populated (multi-lot analysis with apareadas, unified, etc.):

  • Create a THREE.Group per scenario
  • Add toggle buttons in a #scenario-bar div (top-left, below title)
  • showScenario(key) function:
  • Toggle group visibility
  • Update stats panel content
  • Update legend if needed
  • Active button gets .active class (dark background)
  • Lot dividers show/hide based on whether lots are unified

Reference Example

No sample HTML ships with this skill — build each viewer from the utility functions and geometry pipeline above. As a baseline, a representative NYC output (exact tax-lot polygon, contextual base + tower) renders: the lot polygon as the ground plane with vertex markers and edge-length labels, red setback zones between the lot line and the dashed inset polygon, a translucent blue base volume extruded to the street-wall height, a smaller tower volume inset from the base above it, the amber height-cap plane at the maximum height, dashed height reference lines with labels, and the title/stats/legend/controls overlay panels in the four corners.

Step 5: Save File

Save the HTML next to the source report with zoning-envelope- prefix and the same slug:

  • zoning-analysis-250-hudson-st.mdzoning-envelope-250-hudson-st.html

Open the file in the browser after saving.

Notes

  • Dependency: This skill requires a zoning analysis report. It does not perform zoning lookups, coordinate conversion, or regulation parsing — that's the analysis skill's job.
  • Units: NYC reports use feet. The unit field in the Envelope Data block determines all labels and scaling.
  • Camera: Position proportional to max lot dimension. PerspectiveCamera(35) with OrbitControls.
  • Multi-lot: When the report includes scenarios, generate toggle buttons. Use simplified rectangles if individual lot polygons are not available in the report.

Final Step: Disclaimer + Marker (required)

Zoning envelopes are regulatory output. The generated HTML viewer must carry the canonical disclaimer from rules/professional-disclaimer.md:

  • Render the disclaimer text verbatim in the stats overlay footer: "This is an AI-generated analysis for preliminary planning purposes. All findings must be verified by a licensed professional before use in design, permitting, or regulatory submissions."
  • End the HTML file with the marker on its own last line: <!-- architecture-studio:requires-disclaimer -->

The marker is a single end-of-file sentinel — it appears exactly once. The post-write-disclaimer-check hook mechanically verifies .md reports; embedding the same marker in the .html output keeps this skill lintable against the same contract, and the source zoning-analysis report it reads from already ends with the canonical block.

How to use it

Copy the folder

Take alpacalabsllc/zoning-envelope from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.