mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 02:41:17 +00:00
Tommaso's decision (snaporca-x0kd): face orientation is hardwired perception -- a toddler reads
a face's roll and verse with no instruction -- so the connector is a face by default and the
conventional disc + roll quadrant stays, selectable, for users who expect it.
Preferences > Control > Camera > "Draw mate connectors as a face", default ON, key
design_connector_face_glyph. Read every frame rather than latched, so toggling takes effect on
the next repaint -- a look you cannot A/B without restarting will not get compared. Verified on
the rig: unchecking it switches the viewport to the disc live, no restart.
WHY A RELIEF AND NOT A DRAWING. A flat face in the connector's plane foreshortens by
sin(elevation) and collapses at a grazing view exactly like the quadrant it replaces -- measured,
the quadrant falls 89 -> 20 -> 3 -> 0 lit pixels from 47 degrees to edge-on. The relief does not:
its silhouette carries the information. So the glyph is a small shaded solid, painter-sorted,
lambert-shaded against a light fixed in CAMERA space so orbiting does not swing the shading.
THE MUZZLE, AND THE MISTAKE THAT NEARLY LOST IT. It is the only feature standing along +Z, so it
says which way the connector points and it is all that survives edge-on. Two errors on the way:
1. I built its footprint from height*tan(draft) and got a needle. The real base OVERHANGS the
crest at both ends (0.062 nose, 0.034 tail) and that overhang is what makes it a wedge. Base
now lifted straight off the mesh.
2. Worse, I chased fidelity. Scaled honestly the ridge is 11.3 mm on an 83.3 mm face -- 13.6 %
of the width -- and at 22-48 px that is a scratch. Tommaso looked at it and could not find
the muzzle at all, which is the only test that counts. A glyph is a symbol, not a scale
model, so it now gets two deliberate exaggerations, and COLOUR does most of the work:
muzzle share of lit pixels at 90/16/6 deg -- body tone 14.8/11.3/17.5 %, accent gold
18.3/19.2/23.9 %, accent gold at 1.8x width 23.5/25.2/31.2 %.
The accent is the same gold the disc spends on its roll quadrant, so it stays this tab's "here
is the direction that matters" colour. Polarity is still on the Z arrow's head; nothing collides.
A connector whose ROLL COULD NOT BE DERIVED keeps the disc treatment whatever the preference says.
A face asserts a definite orientation, and asserting one for a roll that was never derived is the
same confident lie that got billboarding rejected.
Geometry is emitted from the part by docs/design/mate-connectors/emit_glyph_table.py, not
hand-drawn, so glyph and printed connector cannot drift: 12-vertex outline, two eyes, chin bar,
cheek dot, and the snout wedge. Crest 29.0 mm / 6.58 mm drop / 13.1 deg against the review's
28.3 / 6.61 / 13.1 on the B-rep.
Also fixes extract_outline.py, which walked w.Edges: OCC returns them in storage order, not ring
order, ignoring per-edge orientation, so the outline was scrambled -- 45 points and perimeter
6.380 where a clean ring gives 31 and 3.335. Every measurement in the design notes was re-run.
The correction reversed one earlier finding: handedness does NOT read on its own (5.4/8.0/9.1 %
different from its mirror, not the 32-35 % the scrambled ring produced), so the cheek dot is
required rather than merely nice.
RIG-VERIFIED on Xvfb :12 against a 60x40x10 box with a face+edge connector: the face renders with
both eyes, ears, chin bar, cheek dot and a gold muzzle standing proud; the Z arrow degenerates to
its ring when viewed down the axis; and the preference switches to the disc live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
2.7 KiB
Python
66 lines
2.7 KiB
Python
# Pull the bear's true silhouette and feature positions out of the supplied male B-rep, so the
|
|
# simplification study starts from measured geometry instead of a tracing of the flat drawing.
|
|
#
|
|
# The part's native frame (make_female.py): flat back on Y=0, relief rising to Y=+17.27, the FACE
|
|
# carried by X and Z. So the face plane is XZ and the silhouette is the outline projected along Y.
|
|
import os, json
|
|
import Part
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
s = Part.Shape(); s.read(os.path.join(HERE, "bear.step"))
|
|
sol = s.Solids[0]
|
|
bb = sol.BoundBox
|
|
print(f"bbox X {bb.XMin:.2f}..{bb.XMax:.2f} Y {bb.YMin:.2f}..{bb.YMax:.2f} Z {bb.ZMin:.2f}..{bb.ZMax:.2f}")
|
|
|
|
# The back plate face: the planar face whose normal is -Y and which sits at Y=YMin. Its outer wire
|
|
# IS the silhouette; its inner wires are the eye holes.
|
|
best = None
|
|
for f in sol.Faces:
|
|
if f.Surface.__class__.__name__ != "Plane":
|
|
continue
|
|
n = f.Surface.Axis
|
|
if abs(abs(n.y) - 1.0) > 1e-6:
|
|
continue
|
|
c = f.CenterOfMass
|
|
if best is None or c.y < best[0]:
|
|
best = (c.y, f)
|
|
y, face = best
|
|
print(f"back plate at Y={y:.3f} wires={len(face.Wires)} area={face.Area:.1f} mm2")
|
|
|
|
def wire_pts(w, tol=0.05):
|
|
# ORDER MATTERS and w.Edges does not carry it: OCC hands the edges back in whatever order the
|
|
# face stored them, so concatenating their discretisations gives a scrambled ring. The first
|
|
# version of this script did exactly that and emitted an outline with 7 duplicated points and
|
|
# twice the perimeter it should have. OrderedEdges walks the wire, and each edge is reversed
|
|
# when its own orientation runs against the walk.
|
|
pts = []
|
|
for e in w.OrderedEdges:
|
|
d = e.discretize(Deflection=tol)
|
|
if e.Orientation == "Reversed":
|
|
d = list(reversed(d))
|
|
for p in d:
|
|
pts.append((round(p.x, 3), round(p.z, 3)))
|
|
# drop consecutive duplicates
|
|
out = [pts[0]]
|
|
for p in pts[1:]:
|
|
if abs(p[0]-out[-1][0]) > 1e-4 or abs(p[1]-out[-1][1]) > 1e-4:
|
|
out.append(p)
|
|
return out
|
|
|
|
data = {"outer": None, "holes": []}
|
|
outer = face.OuterWire
|
|
data["outer"] = wire_pts(outer)
|
|
for w in face.Wires:
|
|
if w.isSame(outer):
|
|
continue
|
|
pts = wire_pts(w)
|
|
xs = [p[0] for p in pts]; zs = [p[1] for p in pts]
|
|
data["holes"].append({"pts": pts,
|
|
"cx": round(sum(xs)/len(xs), 3), "cz": round(sum(zs)/len(zs), 3),
|
|
"d": round(max(xs)-min(xs), 3)})
|
|
print(f" hole: centre ({data['holes'][-1]['cx']}, {data['holes'][-1]['cz']}) dia {data['holes'][-1]['d']}")
|
|
|
|
print(f"outer wire: {len(data['outer'])} points")
|
|
json.dump(data, open(os.path.join(HERE, "bear_outline.json"), "w"))
|
|
print("WROTE bear_outline.json")
|