
:: 4/14/2026 menu view
So it goes as follows, the LHS has 3 sections in this order
>files
  **currently open pattern overriding frame x
  *currently open frame
  >current project folder
    >current frame (the currentyl open frame in the viewer)
    >(other frames in the directory)
  >(cached history of other projects folders you've opened recently I guess) 
>patterns
  >project patterns (in your project dirs pattern folder and its dependency patterns)
  >builtin patterns (from jonits)
  >beta patterns (from joints/beta)
>shop
  >timbers
  >joints
I guess you have a radio toggle to pick which ones to show? Or just collaps projects and patterns it's fine...
The advantage of the radio toggel is that you can remove the top level folder in teh view...


-clicking on patterns opens them in temp override viewer
-you can also right click or command cilkc to open in new tab 
  -this open up the pattern as a regular project? or maybe still temp, but without regular project open why not




:: 4/14/2026 drawing generation coad

automatic drawing
- ensures every feature can be solved
- can tag features to be used as reference features and drawer prioritizes drawing to these features
- can tag features with pN to determine order which they get solved in. And P0 means a reference feature
- solve the feature tree such taht each feature is referenced to some other fetaure and teh entire tree is navigatable
- can tag features as no measure
- some feature are coincident (e.g. one face of a row of ortise holes are all coplanar) and only one of the needs to be drawn
  - this can be represented as a "shares plane" or whatever measure so no specila treatment neede necessarily
bonus
-in UI, can select which fetaure to use a reference to draw some other feature
  -so ecah feature has a list of features it is measured to


CoAD based feature refreenceing
we have a way to reference any feature on the cut timbers, so this will be the unit that CoAD drawing is done from

case study: double miter 2x4 with double miter on both end
- first flag one vertex and adjacent edge as p0
  - all miter angles will get mesaured to this edge by default 
- flag the opposite vertex as p1 and the remaining vertices are flagged as p∞ as we don't want to show solutions for them in the drawing
- using default drawer, we get the first set of stuff we need
- next, use custom drawer, we could generate a table referencing each miter angle in the drawing write the matching bevel angle for it into a table
- I guess we could also mark the bevel angle directly on the drawnig
- we could create a feature plane that cuts on the first miter and then use that to generate the bevel angle directly in the 3d view!


:: 3/17/2026 selection / layer view

Lets add a layout view.... We want a little menu on that spans the entire left side collumn that has an option >> expansion thingy to collapse it.

it should have no background or a light transparent background.

it should contain a hirerchacial list view. For now, the list view contais the following:

- timbers
  - list of all timbers
- joints
  - list of all joints
     - list of timbers belonging to that joint (these will be duplicated)
     - list af accesories belonging to the joint

Timbers themselves have a sub hierarchy
-timber 
  -tagged CSGs
    -features

Accessories do not have the sub hierarchy

we may support stuff like locking, fixing and hiding through the layers view so make sure to architect it to support this

when you select an element in the viewer it should also highlight in the layers view and conversely as well
so basically there needs to be mapping sbetween th eselection manage,r layers view and main view.

Propose a refactor to suppor this.


A couple other things, we may show patterns in the layers view using a similar looking UI. patterns are semantically different as they are just a file browser, but they should levearge the same UI to do this. please work this into your plan




:: 3/30/2026 kumiki viewer environment

- when you open a my_structure.py file with the extension
- check the parent folder for an existing .kumiki/project.yaml file which will contain info on the python environment and the girrafecad version, otheriwse create it using the latest version available
- cerate teh python virtual environment to run runner.py in



:: V0 release
-finalize kigumi functions
  -add menu, menu should populate list of patterns and projects in the open folder (maybe ditch the in-viewer thing)
-come up with sensible instalation strat
  -do pip package for release
  DONE-do proper python env on installing extension, maybe add installation loading screen
  -do vscode extension release
-do the layers view for timbers and joints, don't do projects/patterns since you can put that in the menu, or maybe youshould still just do it? idk
-add generic miter joint
-finish docs
  -finish README
  -finish agents doc
  -finish ond remove docs folder
-marketing
  -make simple website
  -make some content drip



::layers TODO
-NO dynamic folder for now, hardcode folders insted
-worry about patterns later, they are logically different even if visually the same
-add tags to timbers and joints, as a 1 tier overlapping folder system
-search bar, autocompletes both names and tags
-tag selection bar
  -has option to choose how many tags to show
  -unselected tags are grayed out, if all grayed out then show everything
  -when one tag is selected, unavaialble tags dissapear
-timbers/joints
  -allow foldering by first tag option
  -clicknig on fol
-joints (separate group, shows up below timbers)
  -allow foldering by first tag option?
  -clicking on joint selects all timbers and accesories contained in it
  -accessories only viewable as children of joints



:: TODO

DONE-kigumi
  DONE-add loading grahpic and do background loading of patterns
  DONE-add refresh button
  DONE-remove examples from sidebar or get it work right
  DONE-only scan patterns folder
  DONE-make proper header and buttons or something...
  DONE?-frames section should omit examples if it starts with "patternbook" as these are patterns
  DONE-patterns section should have a drop down that says "group by patternbook" and then cilcking on the patternbook folder opens the entire patternbook in the viewer
  DONE-clicking on paternbook group folder opens the patternbook
  DONE-allow viewing source on pattern/frames, obvious what to do in workspace patterns, for shipped pattern, it  will open a read only copy it from the dependencies folder?
    -for kumiji shipped patterns also have an option to duplicate pattern in workspace, which will make a copy of teh pattern in workspace for you to modify
    -view source/duplicate source buttons are broken right now...
  DONE?-add special mode for local development??
    -does not initilaize python env stuff and detects that it is in local dev mode
    -does not scan for kumiki (add a notice for local development, and patterns/exampes will show up as workspace patterns)
  DONE-have toggel to choso et open kigumi in separate collumn or in same tab group
  DONE-have settings menu in extension menu to change settings like open in split tab
  DONE-pattern scanning still weird
  DONE-patternbook grouping not working for workspace pattern
  DONE-frames not being detected in local dev mode?
  DONE-group by patternbook still does not have an icon
  DONE-add view source button to frames
  DONE-remove toggle split view button from sidebar, or when you click it it should turn from a single to a double square
  DONE-add integration test for sidebar
  DONE-update examples to be exmaples and not patterns
  DONE-fix examples and ptatern separation, or maybe just get rid of examples
    -remove workspace examples as these are just frames, or perhaps workspace example and frame should follow the same format?
  DONE-add header section to kigumi explorer, it should have 1 box shaped buttons
    -initialize current project, or if project is already initilized, it shows information about the initialized project (or if its a local development mode)
    -remove the "open in split view" button in the top bar 
    -remov ethe open current file in viewer button in the top bar
    -remove the initialize project button in the top bar
DONE-show proper error if you open a non pattern

-publish python package
  -publish to testpypi first
  -setup pypi account
  -publish to pypi

-test the initialize project thing lol
  -need to pbulish kumiki first


-rethink hierarchical feature selection, as you'll want to be able to select 2 features to measure them in the future.

-do new selection hierarchy here are the states
  -nothing selected (show everything)
  -unselected (use transparency)
  -timber selected (no sub selection)
  -timber selected (with sub selection)
  -tagged CSG selected (no sub selection)
  -tagged CSG selected (with sub selection)
  -feature selected
  -I guess just use transparency for outer stuff when focusing in on selection? But you should make distinct states for each of them... but maybe combine the options for changing their look 

-pattern viewer (improved)
  -pull out pattern scanning functionality into librarian.py (workspace, dependencies, and kumiki) and it should return a patternbook and list of frames / examples
  -add searching + tag searching functionality on top of the patternbook/frames/examples
  -allow opening groups of patterns hsaring the same tag as one unit (group by tag, instead of group by pattern book)

-add dovetail shoulder to build a butt joint
-add notching to build a butt joint (you can skip this if you want lol...)

-do assembly metadata
  DONE-create assemblydirection class 
  -for accessories, have assemblydriection but also have locking accessory
  -can you rename accessory btw??
  -add assembly directions to existing joints
    -can have helpers from arragement classes to do this in most cases

  
-add more tests using the right way
-the right way to do hashing is to have all joints hash their input properties and store a _hash property

-decide on layer view
  -is patternbook part of layer view or separate thing
  -figure out how to do show hide option
  -decide on section: timbers, joints (this is fine for now)
    -how do these sections integrate with folders
  -decide on folders being string refs or actual refs when constructing...

-do tinyhouse shed
  DONE-fix joists between intermediate posts, they should mortise and tenon into the posts
  DONE-make floor joist dovetails smaller so they don't intersect with the post tenons
  DONE-add pegs to all horizontal mortise and tenons
  DONE-adjust tenons so the are measured from the outside reference faces
  DONE-add new joints for floor beams
  DONE-add double buttj oint to floor beams
  -lower rafters to be flush with ridge beam and then use housed dovetail joints on ridge beam
  -use cross lap point on the other side (you may need ot update cross lap joint to work on non FAT timbers)
  -make rafter logic not suck lol...
  -do yo uneed/want more rafters?
  


-UI feature select
  -if 2 feature sselected, do measurement rendering between them :O
  -byd efault, noly features are the 6 sides of a timber
    -you can usually not select the end face so how can we tell which is the top/bottom?
      -maybe add an arrow gizmo pointing to the top in the center of teh timber?
      -or for features that coincide with local cardinal dircetions of timber you can flag it
-UI layer view 
  -decide on layer view organization
  -finish ticket system
  -add layers view to left I guess

-UI whatever
  DONE-output proper error if you try and open a non-frame file
  DONE-render kumiki should not open in new tab
  -move focus button, make it more cute
  -add # joints table view at bottom (shouldh include joint type, but you need metadat for this)
  -maybe add "assume upright structure" to limit camera movement with mouse?
  -figure out why box view doesn't render right for the gooseneck, end cut is correct
  -background seleciton should also change font color in viewer
  -blueprint background sucks lololol blueprint should do infinite blueprint plane with horizon instead (horizon backgrounds)
  DONE-add toggle for  debug info in lower right
  DONE-selected info should just be one line
  IGNORE-add front facing direction3d to Frame
  DONE-add some background color choices (colors, gradient,s whatever fun stuff)
  
-perf related stuff stuff 
  -see what you can do about numeric percision of contains_point
  -do yet another perf pass, still pretty slow
  -decide if hashing is ever worth it and or just remove it entirely
    -or maybe come up with a faster hashing method?

-rewrite accessories as timbers probably, so you can leverage any drawing capabilites that are added to timbers in the future
-add accessories to STEP STL and viewer
-and export STEP/STL button back into viewer (comibne/single, step/stl options, defaultable)
-lincoln log joints?
-consider adding in some config object that gets passed through all functions (well ilke one dumpall objcet for joints, etc, so you can easily add carp to it)
-add sensibility checks to arrangement (so basically they intersect at least a little bit)
  -maybe add a generic check intersection function so we can get more precise about it within the joit fuctions.
-do image to frame experiments (some with chain of reason promptitng, some without)
DONE/IGNORE-how dose selection change after refresh?
-highlight timebrs in timber list with selection, or just get rid of timber list?
-document example/patternbook stuff
-after viewer, you should integrate patternbook into viewer
-update agent insturction
-reorg joints by joint type 
-consider making a brace function that both creates a timber and joins it!
-do sympy numeric conversion at the start of each joint... Or just have the numeric stuff cached in the timber or maybe transform class
-add CutCSG::get_surface_curvature(point: V3) -> Optional[Tuple[V3, Numeric, V3, Numeric]] (returns derivative and basis for the derivative) so you can properly do the point on boundary check.. welly ou may as well just have a get_surface_info that returns stuff like its curvature, whether it's an edge or face, and the curvature info.
-create a helper for sizing mortise and timber tenons (so that the "height" of the tenon is always in the mortise timber axis)
  -just copy code from basic_mortise_and_tenon and update tha functino to use the helper
  -you might want to make a sizing helpre class for this to simplify the args?.... idk...
  -prompt address @TODO.txt (202-204)  in the helper, first assert that the 2 timbers are are_timbers_plane_aligned. next assert that the 2 timbers are not parallel. then proceed with the logic @basic_joints.py (97-113). put this helper function in mortise_and_tenon_joint.py


-new joints (need wedge accessory)
  -nuki_end_joint
  -nuki_cross_joint
  -wedged_half_dovetail_mortise_and_tenon

-board support 
  -convert_to_timberlike(board : Board, top_maps_to : TimberFace, right_maps_to : TimberFace) -> Timber (or maybe some temp Timber class)
    


EXEMPLIFY
  DONE-make a set of canonical joint configurations for each joint type to use ine xamples
  DONE/IGNORE-create all_joints.py 
    -exports all joints
  IGNORE-make examples of how to use measure/mark to set parameters in cutting functions
  -write docs for at least one joint function to use as reference for the rest
  -agent prompt examples
  -make sawhores example nice (eh, maybe you need to wedged dovetail mortise and tenons... or nuki joints or whatever)
  -examplify 
    DONE-create some map of example name to example function map so you can dynamiaclly get to all examples
    -would be cool do deep link by into functions/docs in the future...


RENAMEIFY
  DONE-rename measure to locate
    -then you can use measure to measure between two located features (which we don't actually care about, we need to measure between 2 CSG features...)
  -be consistent about function local/golbal naming
    -local input vars should have the suffix  _local
      -some things must be interpreted locally such as "face" or "distance", these d onot have the suffix
    -functions return things in golbal space by default unless _local suffix
  -come up with joint naming rules, in particular if _on_face_aligned_timbers sholud be used or not
    -oFAT on face aligned timbers
    -oFAACaT on face aligned coaxial timbers
    -oPAT on plane aligned timbers (one pair of faces are parallel)
    -oPT on parallel timbers
    -oOT on orthognal timbers
  -do the horseCoAD rename
    -rename code_goes_here to horse
    -change cut_ to layout_? or plan_?
    -rename create_timber to hew?
      -make sure to rename the ones in helperonis as well
      -then boards/panels can be mill_board
      -what about join/stretch/split?
        -join -> glue?
    -add from_joints -> raise function to Frame
      -have an on class member version of it to cuz its cutre that way
    -oscar shed -> tiny snack house
    -kumiki viewer -> horse watcher or barn raising? or 建前 or tatemae
  -consider doing a rename pass on validitiy checkers  
    -assert
    -check
    -is
    -does
    -contains (specific to cutcsg...)
    -are



VIEWER PREP
  -BIG QUESTION, how to "highlight" a feature...
    -seems like you can flag meshes... but you noly want to flag part of the mesh?
  -consider doing csg feature tree thingy
    -face: convex polygon, union (face), difference(face, face), infiniteplane?, rectangularface?
      -is_valid (checks all points are coplanar)
      -get_normal (based on dircetion of polygon points I guess)
    -linesegment: point pair, face intersection?
  -add hash function
  -I think tickets can be done as dictionary... or maybe there are compositoin fo classes?
    -e.g. material ticket, grain direction ticket?
  -is there some way to make orientation/direction generic more like a measuring function?

TESTIFY
  -setup one test template so it can be used as an agent template for everything else
  -add test timber construction thingies on top of the example ones
    -btw you should update your current example so that everything is in the XY plane I think yo uhave soem in the Z plane
  -do better testing on join_face_aligned_on_face_aligned_timbers to make sure it's right....
  -create test runner for examples so the exmaples are also tests :O
  -add test for finger miter joint thingy



DONE-M2 (reward: reimburse camera)
  DONE-VIEWER

-M2.5 V0 release
  -improve ticket class
  -features but no sub features
    -allow clicknig on joint parts
    -allow clicking onfaces to display which face yo uclicked on

-M2.75
  -add panel/board support 
  -update shed example to include panels
  -add screw joint
  -add stl support for joint accessories so you can use framing hardware


-M3 (reward: HNT gordon plane :D)
  -decorative cuts
    -basically joints with 1 timber
    -you'll need to add curve CSG D:
  -add more features to mortise and tenon joint
    -extract out peg creation stuff, it will be useful in other places
      -basically, just figure out the start point and direction of the peg... it will return to you a tuple of prisms to substract from the mortise and tenon timber, and a peg accessory
    -support non face aligned cases
      -clean up the mortise shoulder calculation shit wtf or maybe yo uneed it for non face aligned cases?
    -end wedged mortise and tenon
    -fox wedge mortise and tenon
    -Hōzo-zashi Komisen-dome (ほぞ差し込み栓止め)
      -same as draw bore but with komisen peg,
    -tusked mortise and tenon
    -support tenon_rotation
    -support mortise_shoulder
    IGNORE-add additinoal mortise timbers to mortise tenon joint.. so you can do the thing like the stacked joint on the back post of oscarshed
      -actually better to do this as a house joint and 2 separate mortise and tenon joints. the second mortise and tenon joint will be awkard but it works so why not
    -end_mortise_and_tenon (opitonally extends the mortising timber and offsets the tenon)
      -otherwise calls into mortise_and_tenon above
      -also needs to convert the mortise into an "end cut"
  -basic joints, or maybe move these to "lap joints" file?
    -half_lap (use same code as cross lap joint?)
    -bridle joint (special case of mortise and tenon? nah)
  -sidewedged dovetail mortise and tenon joint (this method is its own method)
  -a few more fancy ones from here https://architizer.com/blog/inspiration/industry/japanese-art-of-wood-joinery/

M3.5
  -drawing support

-M4
  -IFC rendering?
  -more joints
    -splice joints
      -TODO 
    -coner joints
      -mortise and tenon end joint (calls mortise and tenon but positions the tenon in away from the end)
      -half lap
    -cross joints
      -double notch
    -fancy joints
      -the one where 2 beams butt up against 1 post and have the thing that passes through them
  -chamfer render support?
  -define assembly directions for each joint (does this go in meta data?)
  -assembly solver thing :D (lol no) 






:: FUTURE shit

-should I make a "block" class that parents "Timber" and then make a "board" class?
-should we attach meta info to Timbers, it would be nice if timbers were aware of their location on the footprint to assist in finding reference faces for joint operations
- add nominal_size sup port for square and line rule desgins
- make really awesome examples that showcase each parameter of each function



:: TODONE 
DONE-review TestHousedDovetailButtJoint and see if it deserves a camel
DONE-fix feature issues
  DONE-come up with a better term for "named CSG"
    -how about tagged CSG
  DONE-when generating named CSG meshes, it's selecting way too much, maybe you can fix with broad phase? or explicitly test each point against the CSG rather than just using geometry + normal which I assume is what's it's donig right now... or maybe it's using the CSG name which is the same rather than using CSG hierarchy
    -I think it general you should just rewrite named CSG mesh/feature generation. 
  DONE-selecting feature still doesn't highlight properly. I suspect its related to geo generation
DONE-refactor patterns to not actually load the entire pattern
DONE-BUG basic_gooseneck pattern is broken, it's not rendering teh housing tiimber. It works fine when I opne the xample in freecad. in the viewer it shows only 1 timber when there should be 2 so it's not rendering one of the timbers for some reason ,prheaps due to a name duplication? Can you figuer out why.
DONE-fix 0 length prism rendering triangulation
DONE-BUG hrosey viewer seems to run in teh background after closing all tabs... need better cleanup routine, 
DONE-BUG sharde runners that have been stopped stil have their auto relod watcher thingy running, make suer to stop it when you close it or whatever when yo cuose the tab
DONE-BUG pattern viewer thingy does not support CSG stuff
  [tinyhouse120.py] [patterns] loadPattern error: Runner command 'raise_specific_pattern' failed: Pattern 'halfspace_cut' returned Difference, expected Frame can you lok into 
DONE-BUG doubel dildo joint on oscar shed still having z fighting issues.
DONE-get rid of the old step file generatinon thing and then figure out which dependencies are mandatory and just amke them all mandatory, ono optional deps!!
DONE-rename to kumiki/kigumi

DONE-TICKET TODO
  DONE-add reference faces to PTW ticket
    -add a post init step to PTW that does the following
      -assert they are coincident
      -for now require nominal and ptw dimensions to match on reference faces
  DONE-do basic named tagging of features on CSGs 
    -just support tagging faces on halfspace and prism
    -and passing through union/difference
  IGNORE-then must be able to reference named tags inside of joint ticket
    -then when you click on a cut timber, you need to find all joints the timber belongs to to find which features on that joint you clicked
  IGNORE-add some notes for adding broadphase so you don't forget.
DONE-add stdout makers for better profiling
DONE-add settings class for persisted settings in viewer
DONE-add unselecetd transparency option
  -add a slider option so that we can set the transparency of unselected timbers (default 40%)
  -if no timbers are selected, then everything renders as if it were selected (I believe this is current behavior)
DONE/FAIL-add test for joints using max mode or something
DONE/IGNORE-improve integration tests on the viewer. Can you think of some tests to help improve it 
  -epen a test file with a couple milestones, simple timebrs and joints
    -expect timbers show up in raw data
    -expect milestones
  -one test for each of the options to see that they work
  -can we test each of the mouse/keyboard navigation things?
DONE-unselected transparency slider should be inverted
DONE-edge rendering on unselected things should also have transparency applied
DONE-transparent rendering covers shadow and reflection and also covers feature highlights :( fix pls
DONE-probably remove hashing stuff you added
DONE-redo CSG features
  -add an optional name field to CutCSG which creates a hierarchical naming scheme e.g. for mortise and tenon joint
    -timber, top level, the entire timber is considered a named CSG
      -* unnamed csg that is the top level mortise and tenon joint, which is a difference halfspace, rectangular prism CSG
        -halfspace -> "shoulderplane"
        -rectangular prism -> "tenon"
  -each time you click, it navigates a layer deeper
  -feature mesh generation should happen in python on request, not in javascript
    -walk over existing mesh and extract points that land on the feature in question, return resulting tris
  -you still want to be able to select features
    -clicking on selected CSG raycasts and selsects named CSGs within it or features if raycast hits no named CSG
    -ctrl click goes straigh to selecting features
    -not all CSGs have features yet, in these cases, you just select the CSG itself
  -add feature mesh finding/generation/rendering stats each time you click
  -display the CSG/fetaure selection string in the info section
    -hard code in "front/back/left/right/top/bot" face by checking normal when you select a face, so if you select a face that shares a normal with these direction (locally) display it e.g. (selected: post > beam mortise and tenon joint > top end tenon > face (front))
  -keep named features, they are specifically for saw/chisel metadata where we need feature level tagging, however they aren't actually being used at the moment.
      -allow features to be labeled as saw or chisel so we can do cutting instructions
      -for saw, you need to be able to saw a triangel shape (specify 3 vertices)
DONE/IGNORE-improved CSG tagging
  DONE-allow half faces on prism
  -allow full CSG feature naming
  -allow side faces and top/bot flats to be tagged on CSG extrusion
    -would be cool to allow union faces that are coplanar... but I think you can just name 2 features when defining the cutting instructions and check that they are coplanar then
DONE-try auto authoring tests again, this time ask it to use plain_joints as an example
IGNORE-update timber creation to do eval if the expression is even alittle complex and see how honeycomb shed profiling differs
DONE-fix all the crud you have in your safe math functions, maybe just clean up to all floats...
  DONE-girrafe_* methods 
  DONE-remove fast_zero test and fast_equality test
  DONe-safe_compare to take a compare arg
  DONE-do girrafe_* for compare and have all test function call it. all test functions should just be numeric
  DONE-I guess you might want symbolic test methods for some tests?? nah
  IGNORE-cleanup transform class to always use numeric
  DONE-add docs explaining how all this shit works
DONE-do another perf pass  
DONE-add plane aligned variant of join_timbers
DONE-rename join_plane_aligned_on_place_aligned_timbers to join_plane_aligned...
  DONE-update brace example to use join_plane_aligned
  IGNORE-create some example with corner braces...
DONE-I think the meshing n/N in viewer is broken??? doesnt' match profiling times? in general displayed profiling times mismatch actual time  

DONE-splined opposing double butt joint :D (could have drawbore or key)
  -you need to create shoulder_shavings for making shoulder cuts
    -shoulder cuts should be infinite in one direction so can generate the positive and negative for both sides easily
      -I guess you need a full space cut?
DONE-see what you can do about zfighting issues
DONE/IGNORE-ui widgets need to capture mouse inputs so they don't reset selection
DONE-I think you need a 3 way joint, or at least a placeholder for it. Shachisen-tsugi (車知栓継ぎ)
DONE-seemst o freak out after a python cash, need to add error handling
DONE-disable critters or fix it.... (and turn off logging ug)

DONE-do new mouse navigation
DONE-render accessories
DONE-fix reflection rendering and make background nicer?
IGNORE-maybe background colors should inehirt from vscode theme :O
DONE-need plain_double_butt joint examlpe
IGNORE-do layout
  -pop out left tab for hierarchy
  -pop up right tab for gizmos
  -info tab at right/bottom right???
  -bottom left/center section to help you scroll further down to bottom views
    -view options can go here


DONE-decide what "vanilla" project looks like
  DONE-can we just run the cdoe and take what it returns
  DONE-or do we scan for certain patterns
DONE-new shed and irrational angles are failing
DONE-start viewer I guess?
  DONE-setup stdio
  DONE-load example
  ODNE-do basic frame rendering (as boxes) get_bounding_box_prism
  DONE-do camera centering and navigation using 3js package I guess
DONE-add wireframe rendering
DONE-3d view
  DONE-add gizmo in upper right corner
  DONE-add focus button
DONE-move out CSS html and frontend scripts into their own thing
DONE-double/triple/quadruple butt arrangement
  -assert for butt timbers pointing in cardinal directions
  -check_face_aligned_and_cardinal checks that each butt timber is pointing in one of the 4 cardinal direction orthogonal to the receiving timber, and they should all be different
DONE-add tongue and fork joint
DONE-bugs
  -fix the error you have in one of your examples
  -fix csg test error
DONE-move all jonits over to arrangements
DONE-implement tongue and fork butt joint 
DONE-fix step output
DONE-add profiing stats
DONE-add get_perfect_size_in_direction
DONE-change nominal size to nominal half size in direction (this is kind acute actually cuz it can do diagonal size which is something you need, except you probably want get_perfect_size_in_direction)
  -maybe rename to something else
DONE-rename measure to locate
DONE-build-a-butt_joint.py
DONE-I think your hashing the sympy crap so it's taking forever LOL
  DONE-add a toggle to disable the hash check thingy
DONE-need to do perf and figure out why it takes so long....
  -add loading bar on initial load
  -add individual stats for meshing (python -> js round trip. Iguess?)
    -not ecomplexiyt of teh timber
    -write stats to a file?
DONE-be able to tell which timber you clicked on if you clicked on a single timber


DONE-M1 (reward is reimburse H2D LOL)
  DONE-address TODOS in join_timbers
  DONE-create a split timber method that takes a timebr and returns 2 timbers (you splice joint themy ourself later)
  DONE-consider making a better way to store TimberWithJoints and joints class etc...
    DONE-fix tenon joint class
    DONE-add resultant length function after applying joints computerLength(timebr, [joints])
      -get fusino 360 to render just at the rusultant length 
  DONE-add helper render functions
    DONE-extend timbers so joint can be diffed out of it (rather than bool in a long timber, just use an extra long timber when creating the original timber)
    DONE-generic boolean geometry classes
    DONE-use boolean geometery to create negative of tenon cutout timber - (timber - tenon - shoulderplane)
      FAIL-calll this method tenon cutter :D
  DONE-simple joints
    DONE-basic_corner_joint
    DONE-basic_butt_joint
    DONE-basic_splice_joint
    DONE-basic_miter_joint
  DONE-cross_joints
    DONE-basic_house_joint
    DONE-basic_cross_lap_joint
  DONE-splice_joint
    DONE-gooseneck joint 
    DONE-pick a cute one from the book... maybe a gooseneck? (cuz we need it for the double dildo splice joint where we messed up)
  DONE-butt joints
    DONE-mortise and tenon with pegs and end wedges (generic functon, not intended to be called directly) (supports N pegs)
      DONE-mortise and tenon
      DONE-draw bore mortise and tenon
      DONE-through mortise and tenon
      DONE-add offset mortise peg param (so that it tightens the joint up)
    DONE-dovetail lap joint (for the floor joists)
  DONE-make shed example (just add joints)
  DONE-corner joints
    -maybe try the fancy  one we used on the shed  Mitered and keyed lap joint  (箱相欠き車知栓仕口 Hako-aikaki-shachi-sen-shikuchi)
  DONE-brace joint :(
  -improve examples
    -agent prompt examples
    -make sawhores example nice (eh, maybe you need to wedged dovetail mortise and tenons... or nuki joints or whatever)
  -update docs
    -cleanup README
      -clean up venv/makefile/setup nonsense so its simpler...
    -cleanup comments in code and delete morenotes.md
    -generate docs
    - you need a better way to specify timber lenghts... most of the time you don't really care? It should be determined by the joint in most cases.
      - so I guess joints will resize the timber, even tusk tenon joints take a tusk length parameter, tenon joints have a tenon depth parameter etc.. 
      - i think this is how it already is, update the README to explain this
    - move joint section from morenotes into concepts.md and then delete morenotes

DONE-finish mortise and tenon joint non angle stuff and peg depth
DONE/IGNORE-clean up docs on one joint function
DONE-rename joint examples to patterns
  -there can still be a examples folder in the patterns folder
DONE-update morties and tenon to support
  DONE-do cropping on angled mortise and tenon joints
  DONE-make 3 versions
    DONE-perpendicular (simplest)
    DONE-plane aligned
    DONE-no alignment (most complicated)
      -for this one you need to define the shoulder plane a certain distance away from centerline
  DONE-do the mortise_shoulder_distance_from_centerline conversion
  DONE-tenon cutting on braces seems to have broken :(
  DONE-do the generic notch function
  DONE-allow pegs to be positioned in either tenon space or mortise space via parameter
    -remind folks like +y direction depends on orientation of timber, not joint angle
  DONE-add peg orientation parameter (mortise/tenon, ccw_rotation_angle) = (tenon, 0)
  DONE-non PAT stuff
    DONE-update peg logic to assume not PAT
    DONE-mortise hole logic needs to be updated too
  DONE-TODO: peg X position is measured in the tenon length axis, whereas peg Y position is measured in the mortise length axis! This makes positioning pegs on angled braces a lot easier.
    -extract peg function into its own utility... (main tricky part is determining peg length actually, position is more clear...)
    -is this really whaty ou want? and how does this intercact with positing in mortise/tenon space I guess this is a generalized version of that...
  DONE/IGNORE-fix peg depth calculation...
DONE-CutCSG_examples -> CSG_debug_examples.py
DONE-make timber names mandatory and if not provided a random one is generated?
  -if you do random ones they need to be deterministic so that they can be remapped in the viewer
DONE-fix timberlongface methods to take timberface or assert or something
IGNORE-update oscar shed to use updated joint methods?
DONE-create_canonical_ -> create_canonical_example_example_
DONE-move create_canonical_example_ stuff intointo example_shavings.py or sometihng.
DONE-joint consturction objects
  -contains timbers, joint ends, reference faces if applicable
  -contains assertm methods for checking joint sensibility
    -or you could subclass these and assert in their ctors to make it type safe at the tradeoff of more OOP...(nah don't do it, still runtime assert etiher way)
  -contains common computations for the joint configuration
  -contains dumping groups for any additional computed data?
DONE-non FAT/PAT sohulder plane
  DONE-measure_plane_from_centerline  
  DONE-still intersect tenon on shoulderplane and use intersection point to size everything (no changes needed)
  DONE-use this point to generate the notch
    -I guess... you could orient the Z of the notch cutout box with the tenon timber... and orient the XY to be parallel to mortise length axis and perp to tenon length axis... and then cut a box out
DONE-cleanup mortise and tenon joint examples so they actually test out th edifferent parameters and use canonicla ctors
DONE-measuring changes
  DONE-address TODOs in measuring
  DONE-rename marking classes so instafe of DistanceFromFace its PointFromFace or in general <marking feature type>from<reference><constraints>
  IGNORE-add a few tests where we measure/mark together and verify the results
DONE-do the timber class refactor
DONE-fix all type checking errors
DONE-fix type checking errors again lol, and fix for all folders this time...
  -need to decide if you accept ints/floats  
DONE-3 major features
  DONE-measure functions
  DONE-refactor timber class to support multiple timber types
  DONE-add timber metadata
    IGNORE-prepare for CSG metadata as well...
DONE-actually update run_Examples to use patternbook
DONE-update cut notch helper to consider nominal size
IGNORE-consider making marking functions class member function of measurement hehe
DONE-do degrees() function and radians() function
  IGNORE-make a new angle type that wraps sympy so we can typecheck on angle types
DONE-Mitered and keyed lap joint  (箱相欠き車知栓仕口 Hako-aikaki-shachi-sen-shikuchi) using new measuring functions
  DONE-add key holes
  DONE-fix non 90 degree placement
  DONE-add non 90 examlpe D:
  DONE-fix finger prisms
  DONE-default thicknesses are wrong
  DONE-add key accessories
  DONE-fix wedge length
DONE-enable and fix the no python floats check or delete it alltogether
DONE-BUG default lap settings seems to be wrong 
DONE-BUG lap splice joint seems to cut wrongside sometime
DONE-joint reorg
  DONE-basic joints has simplified versions of all joints instead
  DONE-create basic_joints for simplified verison of alle xsiting joints
    -finish mortise and tenon basic jonit
    -create patternbook for basic jonits
  DONE-cleanup joint examples
    DONE-celaup miter joint to use your new construction function thing
    DONE-change rest of joint examples to use standard joint ctor function
DONE-maybe change cutting to have maybe_top/bottom_end_cut 
DONE-add timber size estimation function (really you want a prism BB)
  -then add frame size estimation function
DONE/IGNORE-pull out parts of mortise and tenon function, maybe move to joint_helperonis.py in particular
  -round peg support
  -peg positioning
  -finding the shoulder point/shoulder plane
DONE-BUG Difference is point on boundary is wrong, you need to check the normals of the coplanar faces, if they are the same direction, than point not on boundary, if they are opposite, then point is on boundary.
  DONE-you need to add get_outward_normal function to CSG
    DONE-for union cases, check all children, return get_outward_normal on the point for all csgs that have that point on the boundary and return the average of that
    DONE-for difference cases, first see if point is on boundary on main csg and return that if so, otherwise go through difference csgs, find get_outward_normal on all the csgs that returns true if on boundary and return the average of that (negated)
  DONE-then if point is on boundary of both main csg and difference CSG, check the outward normal, if the dot product is >0 then return false, if it is <= 0 then return true 
DONE-sympy only refactor
  -force everything to be RAtional (change Numeric type back to Exprba)
  -equality_test -> equality_test_with_fuzzy_fallback
DNOE-sympy fml
  DONE-update all equality checks to have fuzzy fallback
  DONE-update safe calls to reuse numeric check
DONE-sympy refactor fml
  DONE-update all types to be expr or float again....
  -probably add points to convert expressions to numeric
    -most likely timber positioning is fine, but all joint computations should be numeric by default
    -you can ignore this for now, your improved heuristic solves this issue...


DONE-all methods have a property `as` that returns TimberFeature 
  -TimberFeature has methods `face` `reference_end` `long_face` etc 
  -so you do long_face.as.face() to do your enum castings
DONE-LocatedTimberFeature -> MarkedTimberFeature
DONE-swap measure/mark
DONE-move out all the get_ methods in Timber.py (change into mark functions)
DONE-be consistent about get/find usage
  -get -> use when you might do an "@property" but we don't get properties so use get, virtually no computation
  -find -> requries some computation
DONE-don't forget to rename class methods in timber.py
DONE-move 
  DONE-more  the helper shit in joint_shavings to  timber_shavings unless I guess if it's really super specific to joints...
  DONE-move TODO stuff in timber into timber shavings
DONE-delete deprecated stuff in joint_shavings and replace with measuring function
DONE-get rid of CSGCut and HalfSpace CSG... omg
DONE-address TODOs in joint_shavings.py
DONE-get rid of extra long edge enums... we don't orient there..
DONE-add short edges to the enum
DONE-maybe revisit measuring naming conevntion
  -we have situations where we want to scribe (multiple timbers) but work with a feature not a distance i.e. mark_closest_point_on_centerlines
  -maybe measurements should be wrapped in an object?.... then you can just measure_face().mark() and then you would do mark_into_face().measure_from_face().distance
  -and i guess then scribe means taking multiple things to one measurement and could by polymorphic to take either local measurements or global features 
  -and we would still need something that takes multiple things to one marking (that marking might not hvae an analgous measure so we can't just scribe().mark())
  -or jsut have scribe return markings that you need to measure... that's probably better... and get rid of the current scribe functions you have...
    -it's just annoying to measure cuz then you need to specify the reference end... but I guess that's a good thing as there fewer args to the mark function!
DONE-swap measure and mark
DONE-think about how you want to make example selector and make it future proof or useful as reference
  -(example metadata, (V3) -> Frame)
DONE-cleanup where to put get_point_on_face and project_point_onto_face
  -probably move these into timber_shavings or something
DONE-CutCSG -> PotatoCSG
  -or CutCSG probably better...
DONE-Moothymoth -> Rule?
DONE-type alias SomeTimberFace = Union[TimberFace, TimberReferenceEnd, TimberLongFace]


IGNORE-transform prob does not need to be frozen...
DONE-come up with consisten import scheme....
  DONE-maybe rename Union to avoid confilct...
DONE-see if you can get rid of FaceAlignedJoinedTimberOffset with a measuring function
  DONE-implementation
  DONE?-do better testing 
DONE-double dildo joint seems to broken in fusion 360
  DONE-fusion 360 broken in general...
DONE-dovetail lap joint
DONE-allow extrude polygon to extrude in both directions
DONE-change joint cut_timbers: Tuple[CutTimber, ...] into a map so you can reference joint pieces by name rather than index
DONE-add back post joints to oscar shed
DONE-delete rhino for now
IGNORE-update fusino 360 to similar as run_examples
DONE-Update Frame to be built from joints
DONE-be consistent about V2 vs (Numeric,Numeric) tuple
DONE-I tihnk the pointing constants in rule are wrong (the comments are certainly wrong)
DONE-fix morties_and_tenon_joint_example
DONE-pull out logic into joint_helperonis.py
  -split the lap helper to do the lap cut on just 1 timber...
IGNORE-add optional key to lap helper?
DONE-gooseneck lap joint
DONE-change gooseneck to return 2 Convex CSGs or support non convex polygon extrusion CSG (errors on some functions or returns None or whatever)
DONE-rafters get added twice in oscar shed
DONE-add extrude polygon csg rendering to fusino 360
DONE-fix the 13:43:54  /Users/peter.lu/kitchen/faucet/kumiki-proto/code_goes_here/japanese_joints.py:221: UserWarning: Gooseneck joint configuration may not be sensible: Joint ends are pointing in the same direction (dot product = 1.000). For a splice joint, the ends should point in opposite directions (dot product should be -1) warning
DONE-do irrational joint test for rendering
DONE/IGNORE-static method in orientation class
DONE-fix or delete goosenecksplice example in japanese joints examples
DONE-do refactors in timber as well...
DONE-REFACTOR TIME
  DONE-create Transform class in rule
  DONE-rename helperonis to something more cute
  IGNORE-make global/local var naming consistent
    DONE-add cursor rules for this
  DONE-update places to use teh local/global stuff in Transform class


DONE-cleanup
  DONE-make it clear dircetion3d and V3 are the same, and get rid of uncessary conversions...
  DONE-rename BACK to BACKWARD (DONE: renamed FORWARD to FRONT instead for consistency)
  DONE-clean up rule tests to not use exact euality...
IGNORE-get rhino rendering working for the following joints ...
DONE-see if you can fix the transform issue in fusion360...  should be able to do as rigid joint with origin
  FAIL-in fusion 360, hwen transforming occurences (components) to their final position.. rather than setting transform2. instead can you create a rigid joint with the global origin to set the position instead. 
  DONE-just do the body move way...
DONE-freecad
  DONE-clean up the run_ freecad stuff so it's more consist and easy to switch between examples
  DONE-add accesory rendering to freecad
DONE-add basic extrude profiles to CSG
DONE-update oscar shed to use mortise and tenon joint
DONE-mortise hole calculation is incorrect please fix
DONE-add Frame class to timber.py 
  -add a check_no_floats method that checks that all the contents of the frame are using sympy rationals, NO FLOATS
  -assert on the check_no_floats method in the ctor to ensure no floats are in the frame
  -update all examples to output frames
  -update fusino360 and freecad to take a frame object
  -get rid of PartiallyCutTimber class while you're at it
DONE-rewrite all joint tests, they are all kinda messed up tbh
  DONE-miter joints
    -uncomment test when get_end_position is fixed
  DONE-just delete get_end_position and the minimal_boundary thing.
  DONE-splice joints
    -delete test relying on get_end_position
  DONE-house joint
    -add test using contains_point
  DONE-cut_basic_cross_lap_joint
    -TODO

DONE-improve tests
  DONE-fix is_point_on_boundary, it's not working... maybe add some tests for it...
  DONE-buttjoints
    IGNORE-fix nearest_point_on_face_to_point_global and uncomment the test
  DONE-mortise and tenon joint
    DONE-fix the T shape orientatin, it's all fucked up right now 
    DONE-peg forward_length / stickout depth tests
    DONE-peg_depth = none case, ensure it's picking the right axis to use for computing peg depth
    DONE-peg orientation should be perpendicular to tenon timber
    DONE-test points in peg hole do not line in timber/mortise csg
    DONE-test points in peg hole do lie on the peg CSG
    DONE-test mortise depth and tenon depth using CSG test functions

  
DONE/SORTA-do the big useless test cleanup
IGNORE-cleanup render utilities, some of them seem unecessary
DONE-rule
  DONE-cleanup all the static ctor stuff, it's confusing
  DONE-create new set of orientation constants for doing timber orientation
    -facing_west/east/north/south (horizontal timbers, top face up, Z-axis rotations)
    -pointing_up/down (identity and 180° Y rotation)
    -pointing_forward/backward/left/right (vertical timbers, +X→+Z, Z-axis rotations)
      -facing_west and pointing_up are both identity orientation
      -all documented with clear canonical conventions
DONE-do mortise and tenon joint
  DONE-fix peg holes in mortise not going all the way through
  DONE-fix default peg size or whatever (maybe jsut udpate eaxmple)
DONE-fix peg rendering with wrong sides ticking out...
DONE-joint rendering
  DONE-add csg function to accessoiers
  -clean up accesory rendering in fusino360
  
DONE-add units construction
DONE-sympy stuff
  DONE-fix fuzzy equal comparison function to use the .equals function. If it returns None then you probbaly do need epsilon test thingy...
  DONE/IGNORE-ask if we can add assumptions anywhere
  DONE-keep geometries as geometries for as data for as possible before converting to expression
DONE-make sure freecad rendering works
DONE-make sure oscar shed rafter pocket works
IGNORE-add some more tests for cross lap joint
DONE-address TODOs is smiple mrotise and sitance 
IGNORE-remove LongEdge 
DONE-update tests to not use floats ever (maybe have a couple float tests in a separate file though)
DONE-rework joint classes
DONE-maybe split griaffe sad :(
DONE-rename "End joint" it's not what you think it's actulaly a splice joint, I think you should call them corner joints instead?
DONE-fix name usage consistence, name should nto be in CutTimebr just use timber name
  -also add name to ctor of timber init function
DONE-check for mutabel data 
DONE-remove mutable position/name stuff from examples and oscarshed
DONE-update all warnings to use warning.warn
DONE-prefix timber methods with _in_local_space (meaning orientation and translation not applied)
DONE-cleanup the epsilon constant stuff (when to use flot vs rational for this?? think baotu ti ab it...)
DONE-change giraffe to use render_timber_with_cuts_csg rather than do it itself
DONE-fix the venv stuff in readme, aeither add venv or get rid of it from readme.
DONE-maybe get rid of corner joint
DONE-fix prism cut rendering stuff for housing join,t noe of the transforms is wrong or fusino 360 is busted
  -specifically, when cutting teh housed timber from the housing timber, the housed timber prism isn't getting positioned right. Is this happening in fusion 360 (probbaly) or in giraffe code?
    -it's definitely a fusion 360 issue

DONE-rename FootprintLocation to footprint location Type
DONE- getInside/OutsideFace(timber, footprint)
DNOE- getFacingFace(tiber, direction)
IGNORE- create readme
DONE- cleaunp morenotes (maybe delete it, all comments should be in code)
DONE- start working on docs
DONE- axis aligned timbers
DONE- rename face aligned to face parallel timebers
DONE- 2 faces can be oriented (same normals) or opposing (opposite normals)
DONE-rename "width vector" to "plus_x_width_vector" or "plus_x_width_vector_of_created_timber"
DONE-add helper asserts that does symbolic check if ratinoal otherwise fuzzy
  -check_parallel,perpendicular
DONE- note down default orientation of timbers when created in vertical and horizontal position
  - when laying horizontal, the orientation should always be consistent such that either the width or height of the timber runs in the Z axis direction
DONE/IGNORE- update timber join functions to only care about timber axis vs faces (X/widhth axis, and Y/height axis)

IGNORE- update timebr sizing notes so its really clear which size is which axis
DONE- decide on default orientation for horizontal timebrs
DONE- decide on default orinatiton for vertical timebrs on footprint
IGNORE- update join_timber to  only take axis to align with up/down rather tha face (I guess you can add a flip180 optional parameter....)
IGNORE- cerate join_timebr_raw that takes a raw face normal 
IGNORE- you need a better system for determining timber orientation after each timber creation operation, have sensible defaults 
  IGNORE- when joining 2 vertical timberss top face always faces up by default
  IGNORE- when joniing 2 horizontal timbers, top face laways faces?
  IGNORE- can you throw in a footprint to control which side top fac faces??



:: END TODONE

:: 3/30/2026 math cleanup prompt

Please review rule.py and all math operations in the code base.

At some ponit Itried to make everything symbolic but realized that would accumluate too many symbols and make things impossibly slow so I switched back to floats but there are some hacks and stuff in the code that make thin sugly. Here's what I want

when creating timbers from literals, use symbolic math everywhere possible and if symbols get even a little too complicated, eval down to float
symbols should never exceed more than like 10 symbols, so lambify/numpy probably not necessary? stick to evalf? However creaet a giraffe_evalf function that calls evalf rather than call evalf directly. Basically I don't trust evalf and worry it will cause issues down the road so I want t obottle neck it all in my operation so I can do stuff like lambidy or timeouts if needed
remove all the timeout and weird hacky stuff I did in rule.py
use the safe_* math operations as much as possible. these check for symbols and shortcut to evalf if the symbols accumulate too much. in practice, they can always just eval down to floats.
Any suggestions in the future? I think in many cases I don't even need to try and preserve symbols. So should we create numeric_* math operations that automatically collapse everything down to floats and prefer using these in most places. For example, CUTCsg are easily supre complicated and shouldnot really attempt to accumulate floats as they aren't used as features to generate anythnig else.


:: 3/8/2025 viewer next steps
DONE-update vscode plugin to use python IPC or something
  -probably wrap in an interface s you can use websocket or stdio or whatever

DONE-triangulation in python
  -try triangulating CSG in python first, you can add more triangle data in the future but for now just need tris
  -actualy I think you want to store fetaure information as features need to be highlightable, not trias correspond to facees, you need to store edges and points separately
    -I guess after generating mesh you walk the mesh to tie back to features :( hope that's not too slow

DONE-smart refresh
  DONE-add filewatcher
  DONE-add hashing on timbers to determine which ones changed
  DONE-the runner keeps a hash map
  DONE-then only generate triangles for stuff that changed

IGNORE-doc tab support?

IGNORE-debug console lol

DONE-select support
  -select timber highlights it in 3d

DONE-navigation support
  -by default camera center is in center of focused objects, can be moved in view plane by panning
  -middle mouse pans
  -scroll wheel zooms and pans towards where mouse location is (in particular, it zooms to the mous eprojected onto the focal plane which is parallel to the screen and intersects the mouse orbit center)
  -pressing F resets focus (using lepr)
  DONE-right click drag rotates camera (or maybe just left click...)
  DONE-maybe include cube gizmo with preset views
  -ortho perspective toggle?
  DONE-must lerp between all transitions!!!

DONE-cute backgrounds
  DONE-creamy gradient background
  DONE-simple drop shadows? (you can fake this one)
  DONE-simple reflections? 
  -fun horse walking trhough background or osmething
    -still walking trhough structure
    -slow down speed and spawn rate a lot. jumps should eb more subtle

-simple list view
  -needs parent list hirearchy support
  -for now just show a list of timbers
    -double clicking on a timber focuses in on it
  -also show list of joints
    -double clicking focuses in on the joint (you probably need some joint center metadata, but you can just derive from the 2 timbers that make the joint for now)

-save as STL or STEP or whatever button

-patternbook integration 
  -patterns are loaded in a hierarchy (horesCoAd < imported libraries < user space)
  -"try me" button creates a copy of teh pattern script in user space with comments indicating that this is temporar yand ovewriting
  -"view source" button takes you to github or something? or maybe just makes a copy in user space or whatever

-bonus feature highlighting
  -clicking on a timber that is in focus highlight sthe featuer on the timber
    -umm, but yo udon't want to do this when too much is selecetd, maybe only do this if a joint or single timber is selected

-bonus, simple measurement support??
  -identify 2 features and draw a distance line between them, lines always show up above everything
  -distance lines can be clicked on which highlight the fetaures they measure from, hence features need to be selectively rendered

-bonus ortho view drawing support
  -take simple measurement support and then render a single timber in ortho view
  -you need a method to check if 2 timbers are the same or rotatinos/reflections of each other
  -maybe there is a likea. "drawing page" that lets you group timbers into pages, and then select how to orient timbers



:: 2/5/2026 CSG feature notes

CSG creates features, I think we can reasonable identify all faces and edges from intersections in our potato CSG code
After CSG gets triangulated, when we click/hover we can raycast onto that triangle
So we want to reveres map from that triangle to some feature
Face features can be uniquely identified by normal and boundaries
edge features are harder, as you can't click exactly on an edge, instead we can check proximity, however we only want to click on the edge if the triangle we clicked on a face that forms that edge. We can map to teh faec first, and then maybe in our csg logic we can find all edges on that face :O

Ok, so we only need to do the following:
-find the fetaure we want to track, this is either a primitive CSG feature or a derived one in the derived case, I think it's really only edges fromed from halfplanes and prism differences (e.g. the edges of a tenon shoulder)
-so we need CSG feature and we need to be able to send it through the CSG tree to diff away stuff from it.
-and CSG features can be primitive CSG features or derived ones. For derived features... maybe the noly derived feature we support is differences between some primitive CSG feature and the uncut timber prism :O, that seems reasoabel to me?

3/22 actual plan:

interface looks like:
`check_feature(csg, feature) -> bool`

feature are defined when constructing the CSG tree. We can limit it to the following 2 cases:
- as as an edge or face feature of a primitive
- as an edge feature in a difference/union of 2 face features

features are stored in a tree
- ecah csg has a feature field (should I just derive from the Ticket class so you can use the name field? probably not??)
- derive_fetaure(csg, feature_field) -> CSGFeature
- feature_field is CSG class specific, it can refer to child features in union/difference, in primitives it refers to the primitive geometry

the final returned csg has a ticket that points to a feature in the top level Difference(ptw, [negative_cuts]) 
- you will need to store this object now in cut timber instead of building it on the fly



:: 2025 documentation

-installation
-quick start 
-simple reference
  -simple examples of how to use each method with default parameters (axis aligned, face parallel only)
  -ALWAYS use Sympy types
-terminology
  -detailed examples explaing all cnocepts
-advanced reference
  -detailed examples outlining every parameter (generate from python files)
-appendix shit

:: 1/6/2026 metadata

-timber metadata 
  -material
  -reference face(s) 
    -used for generating drawings

-joint metadata
  -assembly order
  -assembly DOF
    -by combining order and DOF you can validate if it's possible to assemble
  -feature information (maybe just create a dict of named features for now and fill out the info later)
  -CSG metadata
    -named features


we want to generaize name/parent so we can build ahirerachy, 
there is one special distiction which is that there are Construction and Concept names, Construction maps to actual objects or are used to organize actual objects. Concepts map to things like joints and features

I guess for now just mash them all together but flag them as such. when rendering in something that doesn't support concepts, just omit them, or we can extract concepts into a sepaarte tree but use the same hierarchy (if they were parented to a construction, then just create ap roxy for the construction in the concept tree)


class TicketType(Enum):
  Folder
  Timber
  Accessory
  Board
  Joint
  etc

class Ticket
  name
  parent
  type: TicketType
  def is_type()
  def is_concept()
  def is_construction()



:: 2/24/2025 measure/mark rules

we have the following measurements
LocatedTimberFeature = Union['Point', 'Line', 'Plane', 'UnsignedPlane', 'HalfPlane', 'Space']

And a bunch of different markings which are local to timber features. 

But markings follow the general pattern

mark(measured_feature, timber, feature)

and then their measure function returns another measured_feature, they type of which should be determined from mesaured_feature and feature 

we want a set of rules to determine what feature type gets returned

:: 2/5/2026 JS<->Python interaction DREAM
viewer js calls frame manager python script that holds hores code and hot reloads the target script and runs it and stores the resulting frame. The frame manager has a websocket or whatever that communicates with js. The socket has teh following things:

- on_frame_updated(): fires when target script changes and script was rerun, if success, gives a list of CHANGED timbers names in the frame
- get_frame(): gets the entire frame CSG and metadata for rendering
- get_member(name): gets CSG and metadata for a specific timber
- raycast_feature(member/feature filter, raycast_point_location): returns feature that got clicked on

:: 1/23/2026 viewer planning notes

use three.js to implement viewer as a VScode plugin

base features:
-vscode integration (must be easy to use and integrate with some main script)
-render all timbers
-3d space navigation
-click on timber to focus and see more details
  -render gimbal to show local coordinates

additional features:
-incremental rerendering upon refresh
-timber/folder hierarchy (show/hide/collapse)
-hover over focused timber individual fetaures 
-feature rendering (e.g. render a face in a different color, render joint cuts in different color, etc)

future features:
-2d/3d drawing generation and view (add mesaurements to )
  -a good step 1 would be to show some basic measurements when focused on 1 timber
-assembly explosion


major decision points and things you'll probably need
-triangulation in python or js  (probably in js)
-CSG to mesh function
-add metadata to timbers
-add metadata to CSG/joints for features
  -this includes naming cuts and sub cuts like faces of a prism
  -TODO you probably need some feature CSG classes and then learn to intersect them for new features...
  -TODO this needs to be its own spec

for the naive version of feature highlighting:
  -CSG metadata maps to feature filter functions so that we can do the following
    -you probbaly need some kinda BVH to do this efficiently, but linear search is fine too LOL
  -hover -> raycast triangle -> find feature on timber




:: 1/23/2026 viewer and drawing notes [messy]

We want to be able to generate 2d/3d drawings and be able to highlight features in the viewer.

so the key thing we need is to be able to identify features on timbers. Thihhs comes in 2 flavors
1. when generating diagrams, it needs a list of features to create drawings from
2. when in the viewer, it needs to raycast to see which featuer your hovering over.

2. is more or less covered by 1, perhaps we need some additional data to deal with priorities or something but it should nto be a big deal.

For features when generating drawings, lets worok through some broad examples:

-round draw bore holes need their centers and diameters marked
-mortise holes need their dimensions and depth marked
-angled things can have lengths projected onto axis and/OR show actual angle
-only 1 side of parallel lines of rectangles need to be marked
-view from multiple isometric angles, each measurement need only be drawn once (do this automatically or allow user to specify which measurement goes to which view)
-measurements are specified to some reference point
  -you could auto pick reference point and ensure all referenced points can be referenced to face/edge on the timber I guess?
  -or allow user to specify which feature to reference to in the drawing
-automatic marking generation will probbaly make too many marking so maybe remove some?
-I think in general we need to make suer all features are solveable, have priority on some fetaures, 
  -but prehaps we just name some featuers and do due diligence in coding to ensure everything else is solveable

examples of features we want to mark:

-distance between 2 parallel faces
  -choose side perpendicular to these 2 faces to draw on (or with smallest dot product)


-special rules for face aligned features:
TODO


specific examples:

-mortise example:
  -mortise width and height (distance between opposing parallel faces of the mortise)
    -pick a view to draw it, either top or side, but not from the ends (could use priority system to determine which face of the timber to mark on?)
  -morties depth
    -distance between morties shoulder face and mortise bottom
  -mortise shoulder
    -side view only, distance between mortise shoulder face and timber face that the morties is on 
  -mortise position
    -distance from one side of the mortise to timber face
    -distance from one side of the mortise to the timber end (need rule to pick side, I guess just pick the closest?)
    -(HARD) if multiple mrotises in a row on a timber we may want to do interval spacing (from center) or side to side on the mortises
    -if rotated on face normal axis, mortise, then do a corner of the mortise + angles I guess?
    -if rotated on another axis then mortise depth is measured from 1 side of the 
  

So we need to design a way to 
1. mark features with names 



:: 1/11/2026 advancedtimber class

class PerfectTimberWithin(ABC)
  current timber stuff
  get_nominal_bounding_box_csg_local 
  get_perfect_timber_within_CSG_local
  get_actual_csg_local -> CSG (you can make mesh a CSG class that errors if in a union or on the RHS of a difference)
  is_perfect_timber: check nominal_bounding_box is same as true box, only works if is timber

class Timber
  overrides get_nominal_bounding_box_csg_local 
class Board
  all boards are just stubby perfect timbers, this class adds a few more enums and type guards so that we can type check for board semantics on operations
class MeshTimber
  overrides get_nominal_bounding_box_csg_local and get_actual_csg_local which returns a mesh
class RoundTimber
  overrides get_nominal_bounding_box_csg_local and get_actual_csg_local which returns a cylinder
class PolyExtrusionTimber 
  overrides get_nominal_bounding_box_csg_local and get_actual_csg_local which returns a convexpolygonextrusion
class CSGTimber
  overrides get_nominal_bounding_box_csg_local and get_actual_csg_local which returns a some arbitrary CSG
    
# All TimberLike objects contain a PerfectTimberWithin but morever they Timbers semantically, which is to say Boards are not TimberLike Objects.
TimberLike = Union[Timber, MeshTimber, RoundTimber PolyExtrusinoTimber, CSGTimebr)]



:: 1/27 THE BIG SHAVING/MEASURING RENAME

-We need a better system for naming measure/mark, we have scenarios like
  -timber -> feature 
    -MARK
  -timber/feature -> distance
    -MEASURE_FROM
  -timber/feature -> feature
    -MARK_FROM
  -timber -> distance
    -MEASURE
  -timber/timber -> distance
    -MEASURE_BETWEEN
    -gauge?
  -timber/feature/timber/distance -> distance
    -MEASURE_BETWEEN
    -scribe?

So I think the general rule is 
mark -> outputs fetaure that isn't a point 
scribe -> outputs V3? (and not a Point... probably just get rid of Point? or have point be V3 or whatever?)
measure -> outputs distance
??? -> outputs direction
~gauge -> inputs 2 timbers, outputs distance~
~scribe -> inputs 2 timbers, outputs distance~


Or rather
- mark takes measurements in LOCAL space and outputs features in GLOBAL space
- measure takes features in GLOBAL space and outputs measurements relative to (LOCAL) features
- scribe takes features in LOCAL space and outputs features relative to (LOCAL) features

note we have the following

distance: always taken relative to a feature and can be converted to a feature itself
point: a special case of a feature
feature: everything is a feature at the end of the day :O


-prefixes we could use
  -measure
  -mark
  -gauge
  -scribe
  -get
  -find
  -compute
  -calculate
  -adopt (currently just used in adopt_csg which maybe should be changed to scribe_csg?)


:: 1/8/2026 reference dimension helper classes

measuring functions should look like this:

```
measure_from_some_feature(
  timber_to_reference,
  timber_to_mark,
  reference_feature_on_timber_to_reference,
  measurement_relative_to_reference_feature_on_timber_to_reference_local,
) -> measurement_in_timber_to_mark_local_space
```
for example:
```
timber_cross_section_size_from_reference_long_faces(
    timber_to_reference,
    timber_to_mark, 
    x_face: Optional[LongFace]
    y_face: Optional[LongFace]
    size: V2
) -> size_in_timber_to_mark_xy_local_space
# so (x,y) would have x in the x_face axis and/or y in the y_face axis of timber_to_reference
# only valid if these faces are adjacent
# you typically only want to specify 1 of these faces and the other one is inferred but nothing is stopping you from specifying both
```


sometimes we also want to take reference_feature_on_timber_to_mark and in this case, the returned measurement is relative to reference_feature_on_timber_to_mark. Of course this may only make sense in some cases, in particular that the feature in both cases share the same "basis".

let's make sharing the same basis concrete.

We use the feature definition in concepts.md

first declare the feature e.g. some long edge on a timber (we treat edges as infinite lines)
next restrict the basis e.g. measuring on some face (plane) of the timber
at this stage, we need to do some *magic* to determine the sign of the basis, so if we're measuring on a face of some timber from an edge, then positive is obviously measuring away from the edge onto the face.
for multidimensional basis, we always go x -> y -> z in that order
sometimes the reference timber and mark timber share a basis but have different signs, in this case, the measurement is taking in the reference timbers basis, and then returned in the mark timber basis obviously.
sometimes (often) the basis match but the measurements don't overlap in global space, in this case, project the point orthogonally from 1 basis to the other (TODO need to define this more rigorously, basis is insufficient, we are really talking about a distance in space relative to either a line (1D) or a point (2D) in a plane, in which case we can project to another plane that's parallel (or perhaps better to treat it as a plane/line in space that we intersect with another plane? nah)
or we might be takling about a measurement in an axis or plane (for sizes), in which case no project is needed

Here are some example of feature + basis 

-size on face
-distance from long edge on long face
-distance from end face on centerline
-distance from shoulder plane on centerline (this gets converted to distance from end face internally)

examples of things we want to do

-using join timbers, we want to set lateral offest such that the outside face of the created timber is inset from the outside face of the timber to be joined:
  -how do we do this, the created timber doesn't exist yet???
-position a rafter such that the bottom of of the rafter is 1 inch away from top of the beam
  -same issue as above...
-size a tenon


-measure functions
  -decide consistent naming scheme for measure helpers and document
  -some helpers to add (and use throughout)
    -find_closests_face_to_timber_end_direction
    -plane_from_offset_timber_face
    -also ask AI to see if there are any others
      -especially when it comes with all the timber end if statements
  

----------------
CLEANED UP VERSION OF GENERIC MEASURING FUNCTION

SCRIBE functions
in general we want a very generic scribe function
def scribe1D(distance, from_feature, on_timber, to_timber, from_feature_on_to_timber) (you actually need to constrain by a 1/2 DOF for this to work on edges/points)
notable challenges
-directionality of distance may not be intuitive
-how to constrain DOF on points/edges?

!!!!actually what we'd really like is something like

measure(measurement, feature, timber) -> global feature

mark(golbal feature, timber) -> measurement from local feature

:: 1/19/2026 impl methodology and helpre notes

-joint construction methodology
  -I think for most end joints you want to create a transform that defines where to compute all of the joint features from!!!
    -implemnetation detail, add to cursor rules
    -always use the look at convention, so that +x is right, +y is into the timber (looking at the face), +z is in the joint direction, 
  -try and do the same for butt joints? The challenge here is if it's not perpendicular
  -call it marking_transform?
-joint testing methodology
  -can we do something similar with joint testing? typically, we want to find some point on the face of a timber and work inwards to test various points in the joint
    -so in general, we want to create a transform on the timber and pick points relative to there to test
-variable naming methodology
  -ALWAYS prefix vars in global space with _global
  -try to laways prefix vars in local space with _local
  -unprefix vars should usually be in local space

-feature classes?
  -plane 
  -transform_on_timber

 

:: 1/16/2026 cut agent instructions

implement def chop_lap_on_timber_ends(top_lap_timber: Timber, top_lap_timber_end: TimberReferenceEnd, bottom_lap_timber: Timber, bottom_lap_timber_end: TimberReferenceEnd, top_lap_timber_face: TimberFace, lap_length: Numeric, lap_depth: Numeric, top_lap_shoulder_position_from_top_lap_shoulder_timber_end: Numeric) -> (CutCSG, CutCSG):
1. determine the end the top lap timber based on top_lap_shoulder_position_from_top_lap_shoulder_timber_end and lap_length
2. project the end position onto bottom_lap_timber
3. create half plane cuts to remove the end
4. determine the orientation of the lap based on top_lap_timber_face
5. in the axis orthogonal to top_lap_timber_face find where the 2 laps meet based on lap_deth
6. create prisms to remove the material from each timber resp
7. union with the half lap cuts we created earlier and return the CSG pair


implement cut_housed_dovetail_butt_joint

-there is no lap like the gooseneck joint
-assert that the receiving timber is orthognal
-so the shoulder of the dovetail starts right on the receiving timber and then inset by receiving_timber_shoulder_inset
-create a helper function in joiint_shavings to do the shoulder inset notice, it takes the face to notch, where along the face to notch and how wide (take center and width), how far to inset the shoulder
-the dovetail polygon is a simple trapezoidal shape
-use similar logic as we did on the gooseneck joint to cut the dovetail positive in the tenon timber and then adopt the dovetail CSG into the mortise timber to notch it out.



:: 1/6/2026 timber/board [ARCHIVE]

class hierarchy could look like this:

-thingy (position/orientation, allows converting from local to global coords)
  -block
    -timber
      -MeshTimber
      -RoundTimber
      -PolygonalTimber
    -board
  -joint accessory

-block has faces, but no concept of end/long face
-boards are lie really big stubby timbers, 
  -they have short edges instead of long edges lol.
  -they have sides instead of ends
-boards and timbers have different metadata
-obv, boards and timbers have different joint methods, although their implementations might ultimately be the same and you can use conversion functions if you want to put a timber joint on a board say



:: 1/9/2026 vscode extension 3js viewer

https://chatgpt.com/share/695e878b-da08-800c-a658-6913d1b54ee0

Main challenge:

- allow metadata to be attached to CSG... we want to do this per face which is tricky... as we don't have a good way to identify features in our CSG geometry.... I guess you mainly want "surfaces as result of this difference" or perhaps "surfaces with this normal at this position"

so the next step is acutally to do a CSG expansion

-allow contains_point and boundary to return none, as we will add shapes that can't easily be computed
-allow metadata to be defined on CSG primitives (HARD)
  -primitives define surfaces
  -surfaces must be "preserved" through unions and differences
  -come up with examples to see if this covers our use cases:
    -prism -> timber
    -diff prism -> tenon
    -half plane cut -> shoulder plane
  -would be cool to also mark edges and vertices
-I think the above is doable without embedding info in the meshes, you would just need to have a set of filters (and bounding volumes) to check if some edge/tri matches some feature and we can do this using normals and boundary checks etc
-add CutCSGMesh class and add to_csg_mesh function 
  -hard part, as we also need to add metadata to the mesh
  -I think metadata can be injected based on 


:: 12/30/2025 joint accesorry notes

### PEGS
pegs (could be round or rectangular), rectangular pegs can be inserted at an angle
class RoundPeg
class RectangularPeg:
    size1: Numeric # consistent rule of which axis this is w.r.t to the tenon timber
    size2: Numeric # consistent rule of which axis this is w.r.t to the tenon timber
    rotation: angle in degrees # clockwise rotation when looking at the peg from the insertion side
pegs are always orthogonal to the length axis of the 2 timbers, but they can go in from either side, specified 


QUESTION how to handle peg orientation. do we allow rotating in all idrections? should we make a separat function for tusk tenon  which may want the tenon running lengthwise with the mrotise timber

ANSWER: most generic version is that peg is defined somewhere in the tenon timber, lets oriented in the length axis by default. and then an orientation is applied...

SOLUTION: make a peg class with helpers...

class Peg:
  # peg is always stored in local space of a timber, so identity orientation has peg pointing in the same direction as the length axis of the timber (with the insertion end at the bottom)
  orientation: orientation
  position: V3
  size: Numeric
  shape: square|round 

class PegShape
  size
  shape

create_peg_going_into_face(timber: Timber, face: TimberLongFace, distance_from_bot: Numeric, distance_from_centerline: Numeric, peg_shape : PegShape)


## wedges
wedges are little triangle (with tip cut) pieces of wood going into the tenon end to wedge it in place. 

class wedge:
  # in local space fo the timber, a identity orientation has the point end of the wedge going in the length direction of the timber. 
  # the profile of the wedge (which looks like a triangle with the tip cut off) in the Y axis. Thus thickness is in the y axis
  # teh width of the wedge is in the X axis
  # the 0,0 point of the wedge is at the bottom center of the longer side of the triangle

  +z
   _
  / \
 /   \
/_____\ +x
   ↑
   origin


  orientation: ORientation
  position: V3
  base_width: Numeric
  tip_width: numeric
  thickness: Numeric
  length: Numeric # from base to tip of cut off triangle
  def width() # property alias for base_width


class WedgeShape:
  base_width
  tip_width
  thickness
  length

create_wedge_in_timber_end(timber, end, position, shape: WedgeShape)




:: 12/29/2025 mortise and tenon notes



this function is very generic and does a bunch of things. in particluar we want the following variants which will all call into this function;

      -mortise and tenon
      -through mortise and tenon
      -wedged mortise and tenon
      -wedged through mortise and tenon
      -fox wedge mortise and tenon
      -draw bore mortise and tenon
      -Hōzo-zashi Komisen-dome (ほぞ差し込み栓止め)
        -same as draw bore but with komisen peg,
      -tusked mortise and tenon 


for now, create the super generic 

cut_mortise_and_tenon_DEPRECATED

then do simple_mortise_and_tenon function that has no pegs or wedges

the cut_mortise_and_tenon_DEPRECATED function, it should take the following parameters

-tenon timber
-mortise timber
-tenon end (of the tenon timber)
-size (X,Y cross sectional size of tenon in local space of the tenon timber)
-tenon_length
-mortise_depth (must be >= to tenon length, if none, then it's a through mortise)
-tenon_rotation (ccw rotation of tenon, identity by default )
-wedges parameters (optional)
-peg parameters (otpional)


for implemntation, for now, assert on wedge and peg parameters being none, we will implement them later. also assert tenon_rotation is identity.


stub out the peg and wedge praameter classes below though

class SimplePegParameters
  shape: PegShape
  tenon_face: TimberLongFace # the face on the TENON timber that the pegs will be perpendicular too, only valid if tenon_rotation is identity
  peg_positions: [(Numeric, Numeric)] # first is in length axis measured from the shoulder of the tenon, the second is in the perpendicular axis measured from center
  depth: Optinoal[Numeric] # measured from mortise face where the peg goes in, none means peg goes all the way through to the other side
  length: length of the peg

class WedgeParameters
  shape: Wedge Shape
  depth: Numeric # depth of the wedge cut, note could be different than length of wedge
  width_axis: Direction3D # wedges run along this axis, so the, so when looking perpendicular to this and the length axis, you see the the trapezoidal "sides" of the wedges
  positions: [Numeric] # positions from center of the timber in the width axis
  expand_mortise: Numeric # amount to fan out the bottom of the mortise to fit the wedges, so 0 means straight sides, and X means expand both sides of the mortise bottom by X (total) from straight. 0 by default




:: 12/24/2025 reorg code notes


all core code should live in a "code_goes_here" folder
tests should exists side by side to code (not in their separate test folder)

-timber.py cotains all enums constants and types
-construction.py contains all our timber creation functions
-basic_joints.py contains all basic joint creation functions
-giraffe.py imports all files and rexports them together

don't touch fusion360, rhino, examples, oldcrap folder for now



::12/2025 BREP CUTTING OPERATIONS

make a generic B-rep cutting operations for doing most joints:

-very basic
  -shoulder plane 
    -specified by a position and a normal (infintie plane)
    -created relative to a timber end + direction
  -rectangular prism
    -easy for tenon ends
    -for mortises, do we use the joining timber to locate? Probably not,



Challenges:
- locating rectangular prisms from tenon and mortises
- infinet to finite bounds



:: 12/2025 timber joint rep and  timber length calculatino notes

-cut
  -substractive operation on timber
  -end cuts "set" the length of the timber (either extending it or shortening it) on the end it cut
    -ALTERNATIVE 
      -cut does not care, just does substractive cut. (this is kinda nice)
      -add suppor for infinite timbers (this is really nice)
      -unjointed end cuts can either 
        -be non infinte timber ends (a little ad-hoc)
        -we could use an end cut operation to size the timber (pro) (cumbersome, but most elegant)
  -if cutting on the bot (origin) end, the origin remains in the same place, so the final cut timber has a bottom point below 0
    -ALTERNATIVE: move origin to the very bottom always (ick?)

DECISION
  -timbers defined as is (with origin and length)
  -when a timber has a cut at the end, it becomes infinite in that direction
  -if it helps, you can think of timbers without an end cut as an infinite timber with a default end cut at the length
  
-joint
  -cuts on 1 ore more timbers that connect them


-Cut 
  -implementation detail (sorta)
  -happen at the CSG/brep level
  -if we do the extension thing
    -extending timber end includes cut
    -I guess could/should be used to infer timber length if we go with the "extension" method
  -TODO how are cuts defined, relative to timber origin or relative to a point/face on the timber?
    -maybe relative to timber, in which case a Cut class on its own is meaningless, maybe was well define TimberCut and then [TimberCut] for a list of cuts on a timber (at which point maybe just name it Cut)
-TimberCut
  -A timber with a series of Cuts on it
  -if we do the extension thing
    -has some face-cognition, meaning cuts happen relative to a face,  (this actualy only need)
      -only ends can be "extended", this is very much an ad-hoc operation
-Joint
  -a list of TimberCuts that form the joint
  -also includes joint accesories like draw bore,s fasteners, etc
-CutTimber
  -A timber with a list of TimberCuts?  


:: 11/2025 Reference Stuff

we want to introduce some kind of notion of "ReferencePoint/Line/Plane" for the purpose of positioning timbers relative to each other.
We'll call these classes "ReferenceFeature" 

For example:

I want to create 3 timbers like so:

      
      |
A-----|
  |   |
  |   |
  B   C  


so in this case, we want to use `join_timbers` and align it such that:
- the top face of timber A align with the top face of timber B
- the vertical position set above determines where it will connect with timber C



In general, we want to be able to take a ReferencePoint/Line/Plane of a timber and position that relative to another ReferencePoint/Line/Plane of a timber

We support the following ReferencePoint/Line/Plane of a timber

Timber Vertex  -> ReferencePoint
Timber Edge -> ReferenceLine
Timber Face -> ReferencePlane
Timber Centerline -> ReferenceLine
Timber Face + Timber Centerline -> ReferencePoint



Lets look at the current signature of join_timbers:

def join_timbers(timber1: Timber, timber2: Timber, 
                location_on_timber1: float,
                location_on_timber2: Optional[float] = None,
                lateral_offset: float = Integer(0),
                stickout: Stickout = None,  # Defaults to Stickout.nostickout()
                size: Optional[V2] = None,
                orientation_width_vector: Optional[Direction3D] = None) -> Timber:

In this case we want to be able to do the general positioning instead of just location_on_timber1

There are some limitations since we only have 1 DOF for positioning, in particular we must position against either the Reference Plane that is the top or bottom Face of timber1
In this case, the ReferenceFeature of Timber1 also carries an implicit direction argument, but perhaps we may as well imbue Reference Features with a direction vector? The direction vector must always be perpendicular to the line/plane. 
By convention, the directional vector coming out of an vertex/edge will be perpendicular to one of the faces that the vertex/edge lives on

For the ReferenceFeature of the created timber, it could be:

ReferencePoint
ReferenceLine/Plane that is parallel with ReferencePlane of timber 1




:: 11/2025 DONE Stickout

by default, join timbers joins between midline of timbers but often we want to join from the outside, so we should add modes to the stickout class like

stickoutReference1 / stickoutReference2

enum StickoutReference = CENTER_LINE | INSIDE | OUTSIDE


CENTER_LINE
joined timber
| |
||===== createdtimber
| |

INSIDE
joined timber
| |
| |===== createdtimber
| |

OUTSIDE
joined timber
| |
|====== createdtimber
| |


Also we should probbal yrename join_timbers to refere to timber1/2 as timberA/B instead