mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 02:41:17 +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>
75 lines
3.4 KiB
Python
75 lines
3.4 KiB
Python
# Trim the boxy frame off the female so its outer shape is the bear face itself.
|
|
#
|
|
# Method: take the male's flat back face (the plane Y=0 -- that face IS the bear silhouette),
|
|
# offset its OUTER wire outward in 2D, extrude the result along the insertion axis, and keep only
|
|
# the part of the female inside it. Everything outside is the block frame and goes away.
|
|
#
|
|
# NOTE ON THE NUMBER. The pocket's side walls stand at +0.20 mm from the male outline, because that
|
|
# is the clearance. A trim boundary at +0.10 mm therefore falls INSIDE them by 0.10 mm and removes
|
|
# the side wall entirely rather than leaving a thin one. The script runs the requested value and
|
|
# then measures what is actually left, so the outcome is a number rather than an opinion; it also
|
|
# emits a second variant at an offset that leaves a printable wall, for comparison.
|
|
#
|
|
# Run: /snap/bin/freecad.cmd trim_female.py
|
|
|
|
import os, sys
|
|
import FreeCAD as App
|
|
import Part
|
|
from FreeCAD import Vector
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
MALE = os.path.join(HERE, "bear.step")
|
|
FEMALE = os.path.join(HERE, "BearConnector_Female.step")
|
|
|
|
REQUESTED = 0.10 # as asked
|
|
CLEARANCE = 0.20 # what the pocket was built with
|
|
SAFE_WALL = 1.60 # a wall that survives an FDM nozzle: clearance + ~1.4 mm
|
|
|
|
male = Part.Shape(); male.read(MALE); male = male.Solids[0]
|
|
fem = Part.Shape(); fem.read(FEMALE); fem = fem.Solids[0]
|
|
print(f"female in : {fem.Volume/1000:.2f} cm3, {len(fem.Faces)} faces")
|
|
|
|
# --- the bear silhouette: the male's flat back face at Y = 0
|
|
back = None
|
|
for f in male.Faces:
|
|
n = f.normalAt(0, 0)
|
|
if abs(f.CenterOfMass.y) < 1e-6 and abs(abs(n.y) - 1.0) < 1e-6:
|
|
if back is None or f.Area > back.Area:
|
|
back = f
|
|
if back is None:
|
|
print("FAIL: could not find the flat back face at Y=0"); sys.exit(1)
|
|
print(f"silhouette: back face area {back.Area:.1f} mm2, {len(back.Wires)} wires "
|
|
f"(outer + {len(back.Wires)-1} holes: eyes and mouth)")
|
|
|
|
fb = fem.BoundBox
|
|
y0, y1 = fb.YMin - 5.0, fb.YMax + 5.0
|
|
|
|
def trimmed(offset):
|
|
"""keep only the part of the female inside the silhouette grown by `offset`"""
|
|
wire = back.OuterWire
|
|
grown = wire.makeOffset2D(offset, join=2, fill=False, openResult=False, intersection=True)
|
|
face = Part.Face(Part.Wire(grown.Edges))
|
|
prism = face.extrude(Vector(0, y1 - y0, 0))
|
|
prism.translate(Vector(0, y0 - face.CenterOfMass.y, 0))
|
|
return fem.common(prism)
|
|
|
|
for tag, off, out in (("requested", REQUESTED, "BearConnector_Female_Trimmed.step"),
|
|
("safe wall", SAFE_WALL, "BearConnector_Female_Trimmed_wall.step")):
|
|
r = trimmed(off)
|
|
if not r.Solids:
|
|
print(f"\n{tag} (+{off:.2f} mm): NOTHING LEFT"); continue
|
|
wall = off - CLEARANCE
|
|
# is there any material left at the level of the pocket's side wall?
|
|
sec = r.section(Part.makePlane(400, 400, Vector(-200, 1.5, -200), Vector(0, 1, 0)))
|
|
perim = sum(e.Length for e in sec.Edges)
|
|
print(f"\n{tag} (+{off:.2f} mm) wall = {wall:+.2f} mm")
|
|
print(f" volume {r.Volume/1000:.2f} cm3, {len(r.Solids)} solid(s), {len(r.Faces)} faces")
|
|
print(f" section through the pocket wall at Y=1.5: {perim:.1f} mm of edge")
|
|
if wall <= 0:
|
|
print(f" -> the trim cuts {abs(wall):.2f} mm INSIDE the pocket wall: no side wall remains")
|
|
doc = App.newDocument(tag.replace(" ", "_"))
|
|
o = doc.addObject("Part::Feature", "Female")
|
|
o.Shape = r; doc.recompute()
|
|
Part.export([o], os.path.join(HERE, out))
|
|
print(f" wrote {out}")
|