mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-19 06:53:02 +00:00
The connector work has lived outside the code since 2026-08-05, in a workspace repo with no
remote. It is the basis of a decision that now shapes the Design tab, so it belongs here.
docs/design/mate-connectors/
DESIGN_MATE_CONNECTORS.md seven CAD systems surveyed; the frame-pair model this kernel
already matches; sections 8b/8c on the glyph, and section 9's
four open decisions (D1-D4) still awaiting Tommaso.
bear.step the male, Onshape 2026-08-05T08:27Z, md5 faf228326ee3f971
BearConnector_Female*.step/.stl, BearConnector_Cutter.step
built by make_female.py FROM the real male B-rep rather than
re-modelled, so the pocket is complementary by construction
including every deliberate asymmetry. Fit measured at exactly
0.2000 mm, zero interference, mated hosts proven coplanar.
BEAR_CONNECTOR_REVIEW.md the symmetry-group result: identity 81/81 edges, mirror-x 0/81,
mirror-y 0/81, rot180Z 0/81, rot90Z 0/81, diagonal 0/81 at
0.1 mm. Trivial group, so every PARTIAL view fixes orientation.
extract_outline.py, simplify_study.py, relief_sheet.py, handedness.py, make_female.py,
trim_female.py, fit_check.py, verify_trimmed.py, coplanar_test.py + their sheets
THE DECISION THIS SUPPORTS (snaporca-x0kd): the mate connector is drawn as a simplified BEAR
FACE by default, with the standard disc + roll quadrant + Z arrow kept behind a preference.
Face orientation is hardwired perception -- a toddler reads a face's roll and verse with no
instruction -- and no abstract glyph earns that. Measured against the alternative: the disc's
gold quadrant+tick falls 89 -> 66 -> 37 -> 20 -> 3 -> 0 lit pixels as the camera drops from
47 deg to edge-on, and is a shapeless blob by 16 deg.
WHAT THE SIMPLIFICATION STUDY SETTLED (snaporca-wi3z), all measured off the real B-rep:
The eyes are load-bearing. Same outline and muzzle with the eyes removed stops reading as
a face at every size. Whatever else goes, they stay.
45 -> 22 outline vertices with no loss of read at 22 / 32 / 48 px; the muzzle reduces to
one filled triangle. Three marks plus a cheek dot.
Drawn FLAT the face fails exactly where the disc fails: in the connector's plane everything
foreshortens by sin(elevation). Rendered as its real relief instead, lit pixels at 32 px go
164 -> 210 at 16 deg and 69 -> 120 at 6 deg, and the snout ridge stands proud as a profile
rather than smearing. The glyph must be a shaded relief, not an outline.
Handedness already reads without any added mark -- 32 to 35 % of lit pixels differ from the
mirror, and re-registering by best whole-pixel translation returns offset (0,0), so it is
real shape asymmetry. But it reads only BY COMPARISON. A dot on one cheek makes it local:
34.5 / 37.0 / 36.4 %, and unlike uneven eyes (42 %) it does not read as a defect.
Tommaso's calls: it stays a bear, and handedness must read.
The scripts were repointed at the co-located male and extract_outline.py re-run from here to
prove it -- same 45 outline points, same three inner wires, same 3829.5 mm2 back plate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Flat-shade the faceted ridge key from several camera directions.
|
|
|
|
The point is not a pretty picture. It is one question: does a low-poly solid, flat-shaded,
|
|
let a human read its orientation from an arbitrary viewpoint -- and specifically, is the
|
|
view ALONG the ridge ambiguous between front and back, as the geometry suggests it must be
|
|
in silhouette?
|
|
|
|
Flat shading (one normal per facet, no smoothing) is deliberate: it is what the concept
|
|
claims to rely on, and it is what a CAD viewport with hard normals actually produces.
|
|
"""
|
|
import numpy as np
|
|
from PIL import Image, ImageDraw
|
|
|
|
# ---- the key, same numbers as faceted_ridge_key.scad
|
|
L, W, tf, H, pr, pf, hf = 12.0, 4.0, 0.45, 4.5, 0.22, 0.62, 0.35
|
|
Wf, xr0, xr1, Hf = W * tf, -L / 2 + L * pr, -L / 2 + L * pf, H * hf
|
|
|
|
V = np.array([(-L/2, -W, 0), (-L/2, W, 0), (L/2, Wf, 0), (L/2, -Wf, 0),
|
|
(xr0, 0, H), (xr1, 0, Hf)], dtype=float)
|
|
F = [[0, 1, 2, 3], [0, 4, 1], [0, 3, 5], [0, 5, 4], [1, 4, 5], [1, 5, 2], [3, 2, 5]]
|
|
|
|
LIGHT = np.array([0.35, -0.5, 0.78]) # a headlight-ish key light
|
|
LIGHT /= np.linalg.norm(LIGHT)
|
|
|
|
|
|
def look_at(eye, target, up=(0, 0, 1)):
|
|
f = np.array(target, float) - np.array(eye, float)
|
|
f /= np.linalg.norm(f)
|
|
up = np.array(up, float)
|
|
if abs(np.dot(f, up)) > 0.999:
|
|
up = np.array([0, 1, 0], float)
|
|
r = np.cross(f, up); r /= np.linalg.norm(r)
|
|
u = np.cross(r, f)
|
|
return r, u, f
|
|
|
|
|
|
def render(eye, target, path, size=(620, 460), scale=26.0, label=""):
|
|
r, u, f = look_at(eye, target)
|
|
eye = np.array(eye, float)
|
|
cam = np.stack([r, u, f]) # world -> camera rows
|
|
P = (V - eye) @ cam.T # orthographic: x,y screen, z depth
|
|
|
|
w, h = size
|
|
img = Image.new("RGB", size, (238, 240, 243))
|
|
d = ImageDraw.Draw(img)
|
|
|
|
def to_px(p):
|
|
return (w / 2 + p[0] * scale, h / 2 - p[1] * scale)
|
|
|
|
faces = []
|
|
for face in F:
|
|
pts = V[face]
|
|
n = np.cross(pts[1] - pts[0], pts[2] - pts[0])
|
|
n /= np.linalg.norm(n)
|
|
centre = pts.mean(axis=0)
|
|
if np.dot(n, centre - eye) > 0: # back-face cull
|
|
continue
|
|
depth = P[face][:, 2].mean()
|
|
lam = max(0.0, float(np.dot(n, LIGHT)))
|
|
shade = 0.22 + 0.78 * lam # flat: ONE value for the whole facet
|
|
col = tuple(int(255 * shade * c) for c in (0.86, 0.72, 0.35))
|
|
faces.append((depth, [to_px(P[i]) for i in face], col))
|
|
|
|
for _, poly, col in sorted(faces, key=lambda t: -t[0]): # painter's algorithm
|
|
d.polygon(poly, fill=col)
|
|
|
|
if label:
|
|
d.rectangle([8, 8, 8 + 9 * len(label), 30], fill=(255, 255, 255))
|
|
d.text((14, 14), label, fill=(20, 20, 20))
|
|
img.save(path)
|
|
return path
|
|
|
|
|
|
if __name__ == "__main__":
|
|
t = (0, 0, H * 0.35)
|
|
views = [
|
|
((26, -22, 20), "iso: the reference view"),
|
|
((30, 0, 6), "ALONG +X (from the FRONT, low end)"),
|
|
((-30, 0, 6), "ALONG -X (from the BACK, tall end)"),
|
|
((0, 0, 34), "ALONG +Z (straight down the mating axis)"),
|
|
((2, -32, 5), "ALONG -Y (broadside, grazing)"),
|
|
]
|
|
for i, (eye, lab) in enumerate(views):
|
|
print(render(eye, t, f"rk-{i}.png", label=lab))
|