Articulate.
Articulate Lab · Method

Modelling a real product from photographs

Rebuilding an Exocet Breeze V2 145 windsurf board from four product images and a spec table. The finished hull measures 308 × 65 cm and displaces exactly 145 litres. Getting there took eleven rebuilds, and every one of the detours is a repeatable lesson — about tracing, about colour management, and about the difference between what you render and what the operator is looking at.

The exercise

Given: a deck photo, a bottom photo, two three-quarter shots, and a published spec — 308 cm long, 65 cm wide, 145 L, US box + 2 slot boxes, step tail. Wanted: a parametric model that matches, with the geometry driven by data extracted from the images rather than by eye.

The first attempt was built from a table of sixteen plausible-looking numbers typed by hand. It looked like a lozenge. That is the whole problem in miniature: a careful process applied to invented data produces confident nonsense.

Getting the shape

Use the alpha channel, not a brightness threshold

Cost: an outline with a 2.13 aspect ratio instead of 4.74

Three panels: the Exocet product photograph, its alpha channel silhouette, and the extracted sub-pixel outline plotted against centimetres from the tail
The alpha channel is the silhouette. A brightness threshold picked up the drop shadow and returned an aspect ratio of 2.13 against a real 4.74. The alpha ramp also gives sub-pixel edges, so 201 stations come out smooth rather than stepped. Normalising to the published 65.0 cm width is what makes the one-foot-off check meaningful.

Product shots come with a drop shadow. Threshold on brightness and the shadow joins the silhouette, stretching the bounding box and corrupting every derived measurement. These WebP files carried a clean alpha channel all along — and the conversion to PNG had discarded it.

Read the source file's alpha directly. Check it exists before assuming: alpha.min(), alpha.max(), and what fraction exceeds the threshold.

Verify you have the right curve before you trust the tracing

Cost: a complete rebuild

A supplied SVG contained four large paths. They were assumed to be the board outlines and traced meticulously at 64 stations. They were something else entirely — the resulting tail was 0.694 of maximum width against the real 0.238. Careful tracing of the wrong data.

Trace from two independent sources and compare before building. Here the deck view and the bottom view agreed to within 0.043, and both agreed with the published aspect ratio. That agreement is the licence to proceed.

Cluster your samples where the curvature is

Both ends came out as knife points

Uniform station spacing gives the nose and tail two or three slices each — exactly where the outline turns hardest. The tips render as straight tapers to a point instead of rounded ends.

Cosine spacing: t = 0.5(1 - cos(πi/N)). Dense at both ends, sparse in the middle, same total. Add explicit tip rounding over the last 1.5% of length.

Colour, three ways wrong

Sampling the reference gave pink rgb(212, 82, 169) and blue rgb(1, 120, 205). Putting those numbers into Blender produced a washed-out pastel. Three separate faults, stacked:

Pink and blue swatches shown three ways: as photographed, pasted straight into Blender, and converted to linear
sRGB pasted into a linear socket reads about 2.5× too bright. Blender's colour inputs are linear; a value picked off a photograph is sRGB. Converting properly — c ≤ 0.04045 ? c/12.92 : ((c+0.055)/1.055)^2.4 — took the mean RGB distance from 113 to about 48. The AgX view transform then desaturates on top of that, so match colour under Standard or you are chasing two errors at once.
FaultSymptomFix
sRGB fed as linearEverything too bright and desaturated. sRGB 120 became linear 0.470 instead of 0.187 — two and a half times too bright.Convert before assigning: c ≤ 0.04045 ? c/12.92 : ((c+0.055)/1.055)2.4
AgX view transformAgX deliberately desaturates highlights. Excellent for photographic renders, wrong for matching a product colour.Use Standard when the job is colour accuracy.
Blown by lightingEven with correct values, an over-bright key pushes the surface past its own albedo.Measure the render against the reference at matched stations. Blue went from a mean RGB distance of 113 to 48.

Measure the match, don't judge it

Sample both images at the same fractions along the object, mask out pads and text, and print the RGB triples side by side with the distance. “Looks about right” hid a 2.5× brightness error for three rounds.

Are we even looking at the same thing?

Viewport visibility and render visibility are separate

Two rounds chasing a fault that did not exist

A previous board was left in the file, hidden from renders only. It sat inside the new hull and showed through as a large white shape — in the viewport. Renders were clean, so the fault was diagnosed from a render as a specular blowout, and time was spent adjusting roughness and lighting that changed nothing the operator could see.

When a reported fault cannot be reproduced, the first question is not “what is wrong” but “are we looking at the same picture?” — same shading mode, same visibility flags, same frame.

Material Preview ignores your scene

Blender's Material Preview lights with a built-in HDRI, not the scene's lights and world. Every lighting change made through a script is invisible there until Scene Lights and Scene World are enabled.

Set space.shading.use_scene_lights and use_scene_world on every 3D viewport before debugging any lighting problem.

Compare like for like

A perspective render was compared against a perspective photograph and the mismatch attributed to the model. Two different distortions cannot be differenced meaningfully.

Render orthographic for silhouette comparison, and verify the comparison itself — an aspect ratio of 1.501 against a reference of 4.818 means the measurement is broken, not the model.

Booleans and boundaries

A boundary can only be as crisp as the mesh underneath it

The step tail is an arc crossing the hull at 68.4 cm from the tail on the centreline and 79.5 cm at the rails. Implemented as a height offset inside the surface generator, the edge can only land on existing slices — so it stair-steps at the station spacing.

Insert a dense band of stations across the whole arc, not at a handful of sample points. 120 extra slices over the arc band brought the spacing to 1.13 mm. The same logic applies across the width: 23 columns still leaves ~15 mm jumps sideways.

Watertight is necessary, not sufficient

A boolean that reduced a 12,580-triangle hull to 22 triangles

The cutter was verified watertight — zero non-manifold edges, positive signed volume of 213 L. It still destroyed the model, because it extended past the tail where the board is only 16 mm thick, cutting through the entire section rather than shaving the underside.

Check the cutter against the local thickness of what it cuts, not just its own integrity. And when a modifier stack misbehaves, disable them one at a time and measure the evaluated mesh after each — that isolated the culprit in one pass after two rounds of guessing.

Numbers that check themselves

The most valuable habit in the whole exercise: build measurements that can only agree if they are right.

Plan, profile and cross sections of the Breeze 145 built in the BoardCAD kernel
Plan, profile and sections straight out of BoardCAD's kernel. 308.00 × 65.03 cm, volume solved to 145.00 L against a published 145, and OFO 33.67 against a published 33.70 — a number never used to build the model. The rocker line is labelled as assumed, because it is: nothing published, nothing measurable from the photographs.
CheckWhat it caught
Two views against the spec. Deck aspect 4.818, bottom 4.596, published 4.738.Confirmed the silhouette trace before anything was built on it.
Cross-file consistency. Outline file gave L/W; profile file gave L/H. Together they implied 11.6 cm thickness against a published 11 cm.Proved two separately-drawn references were to the same scale.
Volume by bisection. Thickness solved so the hull displaces exactly 145.0 L.Turns a guessed dimension into a derived one — and re-solves automatically whenever the shape changes.
Feature sanity. The mast track traced to 41.3 cm; a real one is ~40 cm.An independent confirmation that the extraction pipeline was sound.
Independent re-trace. The step arc found by edge detection, then again by colour-region boundary.Two methods, same answer — 0.222 / 0.258.

The recipe

How to model a real object from photographs

1. Get the alpha, not a threshold. 2. Trace two independent views and require them to agree before building. 3. Check both against any published spec. 4. Cluster samples where curvature is highest. 5. Convert sRGB to linear, render on Standard, and measure the colour match numerically. 6. Solve one dimension from a known quantity so the model self-corrects. 7. Compare orthographic to orthographic. 8. Before debugging anything visual, confirm you and the operator are looking at the same picture.

Working rules

The technique above is worth little without the discipline around it. These four came out of the same evening and cost more than the modelling did.

#RuleWhat it would have prevented
1No measurement is used until a second, independent one agrees with it.Meticulous tracing of the wrong SVG paths — a tail 0.694 wide against a real 0.238.
2Related features are measured together, in one frame, and checked against each other.Fin boxes at t 0.206–0.233 and a step arc at t 0.222–0.258 — measured separately, so the step now cuts through the boxes.
3Render what the operator is looking at before reporting. Same shading mode, same visibility flags, orthographic for silhouettes.Diagnosing a leftover white object as a specular highlight, from a render that was clean because that object was hidden from renders only.
4Two failed attempts on the same problem → stop and re-measure.Four successive fixes each computed from the previous one’s damage.

And the honest one underneath

The feedback loop was self-referential: renders from cameras I placed, with visibility I set, judged against my own intent. The operator was the only real signal in the circuit, and was left doing the work of noticing. Verification costs a turn and produces nothing visible — which is exactly why it keeps losing to building, and exactly why it has to be a rule rather than a preference.

The tool we should have started with

Shape3D (and AKU Shaper) are what board shapers actually use. They take precisely the three things extracted here — outline, rocker, thickness — as editable curves, produce a proper lofted surface, and compute volume live as you edit. Features like fin boxes and step tails are first-class objects rather than offsets inside a mesh generator.

The extraction work on this page is not wasted: those curves are the input format such a tool wants. The lesson is about sequencing — check whether a domain-specific tool exists before writing a general-purpose one. A parametric mesh generator in Python was a reasonable idea and a poor choice.

Written 2026-08-16 after the Breeze V2 build. Companion pages: failure log · 30-day programme · the plan