# %% [markdown]
# # Hook for a Ø16 dowel — two parts, a French cleat, one screw into the rod
# 
# The hook's **long axis is the rod's direction**: it runs 53 mm along the dowel against 26 mm of
# drop, a ratio of 2.1. It stands only 10.3 mm proud of the collar — everything it does, it does in
# the rod's own direction.
# 
# ![the sketch this is built from](images/rod_hook.png)
# 
# The hook is **one stroke**, drawn the way a coat hook is: its throat carries the plate's front edge
# straight on down and eases into a curl that winds **tighter as it goes**, while the stroke tapers from
# the plate's full width at the root to a slim **rounded tip turned back up**. From the `Hook for rod`
# reference it keeps the thing that matters: the mouth is **narrower than the throat**, which is what
# makes it retain rather than just support. Every edge of the stroke is chamfered 1.5 × 45°.
# 
# It swallows **Ø23**, through a **16 mm** mouth.
# 
# The collar is a plain closed ring. It is not a clamp and it grips nothing: a single screw goes
# through the hook, through the collar and into the dowel itself, **on the dowel's own axis**. That is
# a diametral pin — the collar cannot slide along the rod and cannot rotate on it, neither of which a
# friction clamp does reliably on timber.
# 
# Two parts, joined by a French cleat, so the hook lifts off — or a different one goes on — without
# touching the collar or the screw in the rod.

# source repo: https://github.com/jeff-dh/SolidPython (solidpython2, editable via pixi)
from pathlib import Path

import rod_hook as rh
from jupyterscad import view_stl

path = Path.cwd()
print(rh.summary())

# %% [markdown]
# ## Two planes
# 
# The part is two 2D drawings at right angles, and they meet because both occupy the same slab of Y —
# between the collar's face at 15.0 and the plate's outer face at 23.5.
# 
# **Looking ALONG the rod — the joint.** Everything about the connection is in this section, which is
# then run 22 mm along the dowel:
# 
#          y=0      12   15               23.5
#           |        |    |                 |
#           |        |    | ############### |  z=+13.3   plate top
#           |  .-‾‾‾‾‾‾-. | #\  lip       # |
#           | /          \  #  \  ######### |  z=+10.0   top of the bevel
#           || |  .-‾-.  | | #   \  ####### |
#           || | / Ø16 \ | | #    \  ###### |  z=+7.0    bottom of the bevel...
#           || |  \   /  | | #     ######## |
#           | \    '-'    / #      ######## |            ...and below it the two
#       ----+--[======SCREW=========]-------+  z=0       parts are simply two flat
#           | /          \  #      ######## |            faces, one on the other.
#           | '----------'  #      ######## |            The screw crosses them on
#           |               #      ######## |  z=-13.15  the rod's axis.
#           |                      ######## |  z=-15.0   plate bottom
# 
# **Looking ACROSS the rod — the hook.** This profile is run 8.2 mm the other way, through that same
# slab of Y, so the stroke leaves the plate *as* the plate: same thickness, no step, nothing to crack.
# 
#     x=0         x=22               x=35            x=48  x=53
#      |           |                  |               |     |
#      ======================================================      the dowel
#      |###########|                                               collar, 22 long
#      |###########|
#      |###########|  <- and behind it the plate, the same 22 long, on the cleat
#      |###########|<------ 16 mm mouth ------->     .-.
#      |###########|                                 (   ) <- Ø7 rounded tip, turned
#       \##########|                                 /  /     34° back up to z = -1,
#        \#########|        swallows Ø23           /  /      5.8 mm inside the rod's
#         \########\                             .'  /       own outline
#          '.#######'.                         .'  .'
#            '-.######'-._                 _.-'  .'
#               '-..#######‾‾--.......--‾‾  _.-'
#                   ‾‾--..___________..--‾‾
#                               bottom of the bight, x=35
# 
# It is one stroke from the plate's crown to the tip, and it is built from the **throat's edge**:
# 
# * that edge carries the plate's front edge straight on down — plate and shank are one face — and
#   then curls through 180° + `TIP_DEG` with a curvature that grows steadily along it (a clothoid,
#   the curve a road uses to ease into a bend). A circular arc has the same curvature everywhere and
#   reads as a ring cut in half; this one opens out low down and winds up at the tip.
# * the stroke's width is laid off that edge outward: the plate's full 22 mm at the root, easing down
#   to the bar over `NECK_L` on a smootherstep, then a gentle taper to the tip. The back edge is never
#   drawn — it follows, as one sweep from the plate's back edge round to the tip.
# 
# `HOOK_LEN`, the length of the curl, is the one size knob.
# 

# %% [markdown]
# ## What the reference part does, and what it costs
# 
# `Hook for rod.step` is a flat 10 mm plate, and its numbers came straight out of the B-rep:
# 
# | | reference | here |
# | --- | --- | --- |
# | throat, what fits | Ø19.5 | Ø23 |
# | mouth, clear | ~14 mm | 16 mm |
# | mouth / throat | 0.72 | 0.69 |
# | bar | 6 mm, constant | 22 at the root → 7.5 → 6 at the tip |
# | tip | Ø9 bulb, turned up | Ø7 rounded end, turned up |
# | edges | 1.5 × 45° chamfer, everywhere | 1.5 × 45° on the stroke and the plate's face |
# | plate | 10 mm | 8.2 mm |
# 
# Three things in that list are the reason it hooks. Two are copied; the third is done differently, on
# purpose:
# 
# 1. **The mouth is narrower than the throat.** What goes in cannot fall straight back out; it has to
#    be threaded back through the gap. A hook whose mouth is wider than its throat is a shelf.
# 2. **A taper, not a constant bar.** For a load hanging in the bight — which is where it ends up —
#    the bending moment is greatest at the root and falls to nothing at the tip, so the stroke is
#    widest where it leaves the plate. The tip still keeps 6 × 8.2 mm, which is a lot of section for a
#    curtain, and the taper is most of what makes the hook read as drawn rather than bent from bar
#    stock. If you want the reference's constant bar back, set `BAR_TIP = BAR_T`.
# 3. **A rounded end, not a point.** A touch fuller than the bar, it is what stops a strap walking off
#    the end — without reading as a knob.
# 
# **And what it does not copy: the position of the mouth.** The reference closes its mouth with the
# *collar*, which sits directly above the bight — that is a hook hanging across the rod, and it is the
# arrangement you rejected. Here the bight is out along the rod and it is the *plate* that overhangs
# the mouth instead. Same job, different part doing it; the 14 mm comes out the same.
# 
# **What fits is found by search, not derived.** A curl whose curvature changes along its length has no
# single radius to quote, and the plate's front edge bounds the throat's upper side. `capacity()` finds
# the largest circle that is actually *held* — hook below it and on both sides of it — and `summary()`
# reports that: **Ø23**, within a few mm of the reference's Ø19.5.
# 
# **And the side profile.** Three things were making it read as a brick with a hook stuck on it, and
# none of them were the hook:
# 
# * The **collar** is the silhouette — the plate hides behind it — so softening the plate did nothing.
#   What was needed was `COLLAR_CHAMFER`, 3 mm at 45° on the ring's ends. It is free, because the tool
#   only reaches `COLLAR_R + 1`: it never touches the bore or the cleat.
# * But the **pad** used to reach the full radius, so its square corner poked out past the ring's
#   chamfered one and kept the corner anyway. Stopping the pad at `COLLAR_R - COLLAR_CHAMFER` is what
#   actually let the corners soften, and it costs 3 mm off a 20 mm couple.
# * The **plate** sat 1.85 mm below the collar, showing as a thin lip with a notch in it where the
#   flank tapered away — a detail that reads as a mistake. It is now level with the collar's underside
#   and invisible from the side.
# 
# And a fourth, on the hook itself: if the tip rises clear of the rod's own outline it reads as a lump
# growing out of the dowel. `tip_clears_rod()` reports the margin — 5.8 mm at `TIP_DEG` = 34° — and
# there is an assert on it. It is the only assert in the file about looks.
# 
# **A note on the chamfer.** It cannot be done by chamfering the 2D outline and extruding, because
# that needs `hull()` of the profile and `hull()` of a hook fills its own throat. Instead each slice
# of the bar is a *convex* disc — narrow, wide, wide, narrow along its own axis — and `hull()` of two
# of those is exactly the swept segment between them. See `_chamfered_disc()`.

# %% [markdown]
# ## The cleat, and why the socket is cut from the release path
# 
# **Above z=+7 there is one 45° bevel, and that is the whole cleat.** It faces up and out. The hook
# drops onto it from above and its own weight slides it down the slope, pulling it in against the
# flat below. Bevel and flat form a couple. The cleat carries the load; the screw only has to stop it
# lifting.
# 
# The screw sits on the rod's axis, which is why the bevel starts as high as z=+7 — the cleat has to
# get out of the screw's way, not the other way round. Ø4 rather than Ø5 on purpose: there is not much
# meat in a 16 mm dowel and a fat screw splits it. And the collar's own hole is the **drill jig** —
# drill Ø2.5 straight through it and the pilot lands on the axis and cannot wander.
# 
# Now the part of the model that is not obvious, and the thing that makes a printed cleat that will
# not come apart. The tempting move is to cut the hook's socket by subtracting the collar's outline
# grown by the fit clearance. It gives a beautiful conformal pocket — and a joint you can only get
# apart by sliding it endways off the dowel, because a 45° undercut wrapped on all sides has no other
# way out. That is a sliding dovetail, not a cleat.
# 
# So `_release_cut()` subtracts something else: **the volume the collar sweeps as the hook comes
# off.** Taking it off is two moves — lift `CLEAT_LIFT` to free the bevel, then pull away. Seen from
# the hook, the collar goes down, then inward. Whatever it passes through cannot be hook.
# 
# Sweeping is cheap because the collar is stored as **convex** pieces — a circle and a five-sided pad
# — and the sweep of a convex shape along a straight move is just `hull(piece, piece moved)`. Two
# hulls per move, exact, rather than a hundred stacked copies. Sweeping distributes over union, so
# doing the pieces separately and unioning is the same as sweeping the whole.
# 
# Running the J along the rod makes this much easier than it was across it. **The whole J lives
# outboard of the collar's face**, so it can never be standing in the release path, whatever shape it
# is. Across the rod it wrapped underneath, and the bar's first bend had to be held clear by hand.

# %% [markdown]
# ## Build
# 
# `build()` writes every scad/stl next to the notebook. Both parts are already bed-oriented — slice
# them as they are, no rotation, no support. They are in **different** orientations, though, and each
# for a reason.

# %%
rh.build(path)
view_stl(str(path / 'rod_hook_collar.stl'))

# %% [markdown]
# The collar stands on its section, **ring axis vertical**, extruded 22 mm up. Every layer is
# identical, so there is no overhang on it at all and the bore comes out round rather than sagging at
# its crown. The first layer is the full cross-sectional area, not a silhouette.
# 
# The one thing in it that is not a vertical wall is the Ø4.4 screw hole, which is horizontal. At that
# diameter it self-bridges.

# %%
view_stl(str(path / 'rod_hook_hook.stl'))

# %% [markdown]
# The hook lies on its **outer face** — the one the screw head lands on — and builds *inward*, toward
# where the collar will be. That is the orientation that makes the two planes agree:
# 
# * the first layer is the whole plate-and-sweep silhouette;
# * above it material only ever goes away — first the 8.2 mm slab ends, then the lip tapers out along
#   the bevel — so **nothing overhangs anything**;
# * the screw hole and the cleat both come out clean, and the hook bends within its layers rather than
#   across them.
# 
# 11.2 mm tall on a 53 × 39 mm footprint — flat and stable. 10.8 g solid in PETG, against 9.7 g for
# the collar.
# 
# The chamfer means the first layer is the silhouette inset by 1.5 mm, flaring out to full over the
# next 1.5 mm at 45° — the plate's face included, so the chamfer runs unbroken from crown to tip.
# That is a normal chamfered-bottom print and still a large footprint.

# %% [markdown]
# ## Fit check
# 
# Both parts on the dowel with the screw, and the dowel already drilled with the pilot the collar
# gives you.
# 
# Checked numerically as well as by eye. Both printed parts are watertight single shells. Assembled,
# the intersection of collar ∩ hook, either part ∩ the dowel, and the screw ∩ either part is **empty**.
# Then, moving the hook against the collar:
# 
# | motion | overlap | meaning |
# | --- | --- | --- |
# | up 1, 2, 4 mm | none | the release lift is clear all the way |
# | then out 2 → 30 mm | none | and it pulls away once lifted |
# | **down 0.5 mm** | 5.5 mm³ | the load direction — the bevel takes it |
# | **in 1 mm** | 265 mm³ | the flat face takes it |
# | **pitched 3°** | 1.5 mm³ | the couple engages |
# | **twisted 2°** | 5.4 mm³ | and so does the cleat's 22 mm length |
# 
# **That last row is now the one that matters.** Running the hook along the rod moves the load off to
# one side of the collar, so it wants to twist the plate rather than pull it forward. Two couples, and
# they have swapped importance:
# 
# * *pitching out* — the load hangs only 4.4 mm off the bearing face, against 36 mm when the hook ran
#   across the rod. This one has all but vanished.
# * *twisting* — a load in the bight acts 24 mm off the collar's centre, and only the cleat's 22 mm of
#   length resists it: about **1.1 × the load** at each end of the bevel. Out at the tip it is
#   **1.9 ×**. That is the reason the plate is the full 22 mm rather than a narrow lug.
# 
# So **hang things in the bight, at x=35, not out at the tip.** The bight is where they will end up
# anyway — it is the low point.
# 
# Straight out without lifting is *not* blocked, and should not be: a French cleat is held by gravity
# and the load, and here by the screw. Nothing about the geometry pretends otherwise.

# %%
view_stl(str(path / 'fit_check_section.stl'))

# %%
view_stl(str(path / 'exploded.stl'))

# %% [markdown]
# The exploded view is the real motion, not an arbitrary pull-apart: up by `CLEAT_LIFT`, then away.

# %% [markdown]
# ## Print the coupon first
# 
# `coupon_cleat.stl` is an 8 mm slice of both halves, taken clear of the screw hole and the hook so it
# tests the cleat on its own. Each half is in its own print orientation, so the coupon is printed the
# same way the real parts are — which is the only way it settles anything.
# 
# It settles `CLEAT_CLEAR`, the one number that cannot be calculated for your printer. Drop one onto
# the other:
# 
# * **lands with a click, no rock** → that is your number, leave it alone.
# * will not seat, or you have to press it → raise `CLEAT_CLEAR` (now 0.3) by 0.05.
# * seats but rocks → lower it. A little slop is fine, the screw takes it up; but if it rocks, the 45°
#   faces are landing on an edge instead of flat.
# 
# Check the first layer before you change the model — elephant's foot on the two flats is the usual
# reason a cleat will not sit down.

# %%
view_stl(str(path / 'coupon_cleat.stl'))

# %% [markdown]
# ## Printing & assembly
# 
# **Slice `rod_hook_plate.stl`** for both parts in one go, or the two `rod_hook_*.stl` files
# separately. Neither needs rotating, supporting or brimming.
# 
# * **PETG** for preference. This is a cantilever carrying a standing load, and PLA creeps — a hook
#   that is tight in January is drooping by June.
# * **0.2 mm layers, 4 perimeters, ≥40 % infill.** The load path is one 45° face and one screw; the
#   perimeters do the work.
# 
# **Hardware:** one **Ø4 × 30 wood screw**. If your rod is metal, tap it M4 instead — same hole.
# 
# **Assembly:**
# 
# 1. **Hook onto collar first, on the bench.** It drops on from above and wants 4 mm of headroom you
#    may not have once the collar is on the rod.
# 2. Slide the pair onto the dowel from the end and set it where you want it.
# 3. Drill Ø2.5 through the collar's own hole. It is the jig; the pilot lands on the rod's axis.
# 4. Drive the screw. 15.35 mm of it is crossing the printed parts before it bites.
# 
# **To move it**, back the screw out, slide, re-drill. **To change the hook**, back the screw out and
# lift the hook off — the collar and its hole in the rod stay put.
# 
# **Which way does it face?** The sweep runs toward +X, away from the `x=0` end of the collar. Turn the
# whole thing end-for-end on the rod and it faces the other way; the part is otherwise symmetric about
# its own mid-length, and the screw is on that centreline.
# 
# ## What to change
# 
# | you want | turn this |
# | --- | --- |
# | a different rod | `ROD_D` — everything re-proportions around it |
# | a bigger or smaller hook | `HOOK_LEN` — the length of the curl; mouth, what fits and reach all follow |
# | a tighter or more open curl | `CURL` — 1 is a circular arc, higher winds the tip up tighter |
# | a more closed mouth | `TIP_DEG` (34° now) — `tip_clears_rod()` keeps it below the rod's outline |
# | a stouter hook | `BAR_T`/`BAR_TIP` in the profile, `PLATE_T` through the thickness |
# | a slimmer or fuller root | `NECK_L` — how far the stroke takes to narrow from the plate's width |
# | a tighter mouth | `TIP_DEG` — it costs nothing in envelope, it only closes the gap |
# | a fuller tip | `TIP_BULB_D`, and `TIP_SWELL` for how abruptly it swells |
# | softer edges | `EDGE_CHAMFER` on the stroke, `COLLAR_CHAMFER` on the collar's ends |
# | a less blocky collar | `COLLAR_CHAMFER` — and the pad has to follow it, there is an assert |
# | a deeper cleat | `BEVEL_H`, and `CLEAT_LIFT` follows it |
# | a bigger screw | `SCREW_D`/`SCREW_CLEAR_D`, and check `BEVEL_Z0` still clears the hole |
# 
# ## Two caveats on the numbers
# 
# `ROD_D = 16.0` is **nominal, not measured**. The STL you started from was 84 bytes of `MESH-MESH-…`
# — this repo's pre-commit hook had already scrubbed it — so none of the original geometry could be
# read back off it, and none of it is copied here.
# 
# Caliper the actual dowel before printing the collar. Timber dowel routinely runs 0.2–0.5 mm under,
# and unlike a clamp there is no take-up: the bore is a plain slip fit and whatever clearance you
# leave, you keep. The screw stops it moving, so a loose bore costs you nothing but a rattle — but it
# is worth knowing which you have.
# 
# **And one on the hook's size.** The proportion — 1.6 along the rod for every 1 down — is measured off
# the sketch, and a ratio is the one thing a screenshot gives you reliably, because it does not depend
# on knowing the scale. The absolute size does: that needs a known length in the picture, and depending
# on whether the widest gold band is the Ø26 collar or the Ø16 rod, the sketched hook is either 71 mm
# long or 43 mm. So the size here is kept near the previous version's and the *shape* is matched
# instead. If it wants to be bigger, `HOOK_LEN` is the single number.

# %%



