open OCADml open OSCADml module Bone = struct type t = { scad : Scad.d3 ; base : V3.t ; tip : V3.t ; joint : V3.t [@cad.unit] ; normal : V3.t [@cad.unit] } [@@deriving cad] let bend a t = let q = Quaternion.make t.joint a in { t with scad = Scad.quaternion ~about:t.base q t.scad ; tip = V3.quaternion ~about:t.base q t.tip ; normal = V3.quaternion q t.normal } let splay a t = let q = Quaternion.make t.normal a in { t with scad = Scad.quaternion ~about:t.base q t.scad ; tip = V3.quaternion ~about:t.base q t.tip ; joint = V3.quaternion q t.joint } let make ?(fn = 5) ?alpha ?colour ?(angle = 0.) ~rad len = let scad = Scad.cylinder ~fn ~height:len rad |> Util.value_map_opt ~default:Fun.id (Scad.color ?alpha) colour in { scad ; base = v3 0. 0. 0. ; tip = v3 0. 0. len ; joint = v3 1. 0. 0. ; normal = v3 0. (-1.) 0. } |> bend (Float.pi /. -2.) |> splay angle let to_scad t = t.scad end module Finger = struct type t = { prox : Bone.t [@cad.d3] ; mid : Bone.t ; dist : Bone.t } [@@deriving cad] type config = { offset : V3.t option ; lengths : float * float * float ; radii : (float * float * float) option ; splay : float option } let config ?offset ?radii ?splay lengths = { offset; lengths; radii; splay } let map ~f t = { prox = f t.prox; mid = f t.mid; dist = f t.dist } let splay a t = quaternion ~about:t.prox.base (Quaternion.make t.prox.normal a) t let extend ?mult a t = let p, m, d = Option.value ~default:(1., 1., 1.) mult in let t' = quaternion ~about:t.prox.base (Quaternion.make t.prox.joint (a *. p)) t in let mid_bend = Bone.quaternion ~about:t'.mid.base (Quaternion.make t'.mid.joint (a *. m)) in { prox = t'.prox ; mid = mid_bend t'.mid ; dist = Bone.bend (a *. d) (mid_bend t'.dist) } let flex ?mult a = extend ?mult (-.a) let make ?splay ?(offset = V3.zero) ?radii (lp, lm, ld) = let rp, rm, rd = Option.value ~default:(6., 5., 4.) radii in let prox = Bone.make ?angle:splay ~rad:rp lp and mid = Bone.make ?angle:splay ~rad:rm lm and dist = Bone.make ?angle:splay ~rad:rd ld in { prox ; mid = Bone.translate prox.tip mid ; dist = Bone.translate (V3.add prox.tip mid.tip) dist } |> translate offset let of_config { offset; lengths; radii; splay } = make ?splay ?offset ?radii lengths let to_scad { prox; mid; dist } = Scad.union [ Bone.to_scad prox; Bone.to_scad mid; Bone.to_scad dist ] end module Fingers = struct type t = { index : Finger.t [@cad.d3] ; middle : Finger.t ; ring : Finger.t ; pinky : Finger.t } [@@deriving cad] let map f t = { index = f t.index; middle = f t.middle; ring = f t.ring; pinky = f t.pinky } let fold f init { index; middle; ring; pinky } = let flipped = Fun.flip f in f init index |> flipped middle |> flipped ring |> flipped pinky let update ?(index = Fun.id) ?(ring = Fun.id) ?(middle = Fun.id) ?(pinky = Fun.id) t = { index = index t.index ; middle = middle t.middle ; ring = ring t.ring ; pinky = pinky t.pinky } let extend ?mult a = map (Finger.extend ?mult a) let flex ?mult a = map (Finger.flex ?mult a) let of_configs ~index ~middle ~ring ~pinky = { index = Finger.of_config index ; middle = Finger.of_config middle ; ring = Finger.of_config ring ; pinky = Finger.of_config pinky } let to_scad t = Scad.union @@ (fold (fun l a -> Finger.to_scad a :: l) []) t end module Thumb = struct type t = { bones : Finger.t ; duct : V3.t } [@@deriving cad] let make ?splay ?offset ?(radii = 9., 8., 7.) lengths = let bones = Finger.make ?splay ?offset ~radii lengths in { bones = Finger.quaternion ~about:bones.prox.base (Quaternion.make (V3.sub bones.prox.base bones.prox.tip) (Float.pi /. 2.)) bones ; duct = v3 0. 1. 0. } let of_config Finger.{ offset; lengths; radii; splay } = make ?splay ?offset ?radii lengths let extend ?mult a t = { t with bones = Finger.extend ?mult a t.bones } let flex ?mult a = extend ?mult (-.a) let splay a t = { t with bones = Finger.splay a t.bones } let adduction a t = { t with bones = Finger.quaternion ~about:t.bones.prox.base (Quaternion.make t.duct a) t.bones } let abduction a = adduction (-.a) let to_scad t = Finger.to_scad t.bones end type config = { index : Finger.config ; middle : Finger.config ; ring : Finger.config ; pinky : Finger.config ; thumb : Finger.config ; carpal_len : float option ; knuckle_rad : float option } type t = { fingers : Fingers.t ; thumb : Thumb.t ; carpals : Scad.d3 ; knuckle_rad : float [@cad.ignore] ; origin : V3.t ; wrist : V3.t [@cad.unit] ; heading : V3.t [@cad.unit] ; normal : V3.t [@cad.unit] } [@@deriving cad] let flex_fingers ?mult a t = { t with fingers = Fingers.flex ?mult a t.fingers } let extend_fingers ?mult a t = { t with fingers = Fingers.extend ?mult a t.fingers } let flex_thumb ?mult a t = { t with thumb = Thumb.flex ?mult a t.thumb } let extend_thumb ?mult a t = { t with thumb = Thumb.extend ?mult a t.thumb } let extend a t = quaternion ~about:t.origin (Quaternion.make t.wrist a) t let flex a = extend (-.a) let adduction a t = { t with thumb = Thumb.adduction a t.thumb } let abduction a = adduction (-.a) let suppinate a t = quaternion ~about:t.origin (Quaternion.make t.heading a) t let pronate a = suppinate (-.a) let radial_dev a t = quaternion ~about:t.origin (Quaternion.make t.normal a) t let ulnar_dev a = radial_dev (-.a) let update_digits ?(index = Fun.id) ?(ring = Fun.id) ?(middle = Fun.id) ?(pinky = Fun.id) ?(thumb = Fun.id) t = { t with fingers = Fingers.update ~index ~ring ~middle ~pinky t.fingers ; thumb = { t.thumb with bones = thumb t.thumb.bones } } let make ?(carpal_len = 58.) ?(knuckle_rad = 5.) (fingers : Fingers.t) (thumb : Thumb.t) = let carpals = Scad.cylinder ~height:carpal_len knuckle_rad |> Scad.rotate (v3 0. (Float.pi /. 2.) 0.) |> Scad.translate thumb.bones.prox.base in let mid = fingers.middle.prox.base and z = Fingers.fold (fun m a -> Float.min m (V3.z a.prox.base)) (V3.z thumb.bones.prox.base) fingers in let heading = V3.normalize (v3 0. (V3.y mid) (-.z)) and origin = v3 (V3.x mid) (V3.y thumb.bones.prox.base) z in { fingers ; thumb ; carpals ; knuckle_rad ; origin ; wrist = v3 1. 0. 0. ; heading ; normal = V3.rotate (v3 (Float.pi /. 2.) 0. 0.) heading } |> translate (V3.neg origin) let of_config { index; middle; ring; pinky; thumb; carpal_len; knuckle_rad } = let fingers = Fingers.of_configs ~index ~middle ~ring ~pinky and thumb = Thumb.of_config thumb in make ?carpal_len ?knuckle_rad fingers thumb let to_scad ?(alpha = 0.5) ?(color = Color.Pink) { fingers; thumb; carpals; knuckle_rad; _ } = let palm = Scad.hull [ carpals ; Fingers.fold (fun l fgr -> Scad.translate fgr.prox.base (Scad.sphere knuckle_rad) :: l) [] fingers |> Scad.union ; thumb.bones.prox.scad ] in Scad.union [ Fingers.to_scad fingers; Thumb.to_scad thumb; palm ] |> Scad.color ~alpha color let home ?(hover = 18.) Plate.{ body; config; _ } t = let centre_row = config.body_centres config.centre_col in let key = Columns.key_exn body config.centre_col (Int.of_float centre_row) in let pos = V3.(key.origin +@ (Key.normal key *$ hover)) and aligned = let column_vec = V3.( normalize (mul ( (Columns.key_exn body config.centre_col (Int.of_float @@ (centre_row +. 1.)) ) .origin -@ key.origin ) (v3 1. 1. 0.) )) in let tented = suppinate config.tent t in quaternion ~about:tented.origin (Quaternion.align V3.(normalize @@ (tented.heading *@ v3 1. 1. 0.)) column_vec) tented in translate (V3.sub pos aligned.fingers.middle.dist.tip) aligned (* TODO: obviously the flex_fingers start isn't super great if I need to adjust almost all the fingers. Should I bother with the two steps? *) let home_curl t = let t' = flex_fingers ~mult:(0., 1.0, 0.5) (Float.pi /. 2.8) t in { t' with fingers = { index = Finger.flex ~mult:(1., 0., -1.) (Float.pi /. 60.) t'.fingers.index ; ring = Finger.flex ~mult:(-0.2, 1., -0.2) (Float.pi /. 16.) t'.fingers.ring ; middle = Finger.flex ~mult:(-0.1, 1., 0.1) (Float.pi /. 20.) t'.fingers.middle ; pinky = Finger.flex ~mult:(-0.5, 1., -0.2) (Float.pi /. 30.) t'.fingers.pinky } } |> flex_thumb ~mult:(-0.4, 1.0, 0.2) (Float.pi /. 8.) let default_config = { index = Finger.config ~offset:(v3 21. 60. 0.) (47.5, 27., 21.) ; middle = Finger.config ~offset:(v3 41. 62. 0.) (55., 31., 27.) ; ring = Finger.config ~offset:(v3 55. 60. 0.) ~splay:(Float.pi /. -25.) (50., 31., 24.) ; pinky = Finger.config ~offset:(v3 70. 52. 0.) ~splay:(Float.pi /. -11.) (37.5, 26., 22.) ; thumb = Finger.config ~offset:(v3 15. 0. (-25.)) ~splay:(Float.pi /. 8.) (52., 30., 28.8) ; carpal_len = None ; knuckle_rad = None } let example = of_config default_config