Monty learns an object by sensing it, and its pretrained memories come from episodes. A CAD part has already been explored: its surface is exact, and the recognition names every face, so a node's label is a fact rather than a conclusion. This package is the join between the two — the part on one side, Monty's own percept and its library format on the other.

{:ok, part} = Quiddity.open("bracket.step")

# What a sensor module perceives here:
{:ok, camera} = Quiddity.Camera.patch([15.0, 0.0, 13.0], [0.0, 0.0, 1.0], distance: 5.0)
{:ok, frame} = Quiddity.frame(part, camera)
{:ok, message} = MontyCad.Percept.message(frame)

MontyCad.Percept.node_features(message)
#=> %{"pose_vectors" => [...], "principal_curvatures_log" => [0.0, 0.0], ...}

# The library a run loads:
{:ok, written} = MontyCad.Training.write(part, "bracket", "bracket.jsonl.gz", spacing: 2.0)
written.nodes
#=> 186

The percept is Monty's own code — Monty.Frameworks.Models.SensorModules.ObservationProcessor over the frame — not a re-implementation, so a node trained from a part is the percept a run would have made. docs/bridge.md says exactly what is faithful and what is not.

The three modules

ModuleWhat it answers
MontyCad.PerceptA Quiddity.Frame as the observation a sensor module reads: semantic_3d, shaded rgba, cam_to_world, and the message Monty builds from them
MontyCad.TrainingVisits over the part's surface, one percept each, with exact labels from the recognition
MontyCad.LibraryThe file: gzipped JSON lines, one record per node, in the shape GraphLM reads

MontyCad.Percept.node_features/1 merges the morphological and non-morphological features the way GraphLM.select_features_to_use/2 does — those are the columns a record stores.

From a shell

mix monty_cad.train bracket.step --object bracket --out bracket.jsonl.gz

--spacing sets the millimetres between visits, --limit the most visits to make. The answer names the file, the nodes, the features held, and how many nodes each feature family contributed.

Writing a plugin: the generators

A run is assembled from modules — a motor policy that decides where to sense next, a sensor transform that turns one observation into a percept. Each has a contract in Monty's own behaviour, and a cluster that will call it wants a description of it first. Two generators write both halves, so what you fill in is the decision and not the wiring:

mix monty_cad.gen.policy follow_chamfers
mix monty_cad.gen.transform surface_smoother

Each writes a module and a test:

follow_chamfers
the modulelib/<your_app>/plugins/follow_chamfers.ex — Monty.Frameworks.Models.MotorPolicies.MotorPolicy's callbacks and describe/0
the testtest/<your_app>/plugins/follow_chamfers_test.exs — it describes itself, it is a motor policy, it forgets between episodes

call/6 and call/3 raise with a sentence saying what to answer, on purpose: a generated body that looks like an answer is a decision nobody made. The policy is :owner_only (it reads a B-rep, and a B-rep handle cannot leave its node); change that in describe/0 if yours does not, and the test will follow the description.

The description is MontyCad.Plugin's contract — kinds, placements, needs — and MontyCad.Plugin.conforms?/1 says what is wrong with one that a cluster cannot use. A cluster asks over the protocol; this package asks the same question in a test.

Install

def deps do
  [
    {:ex_monty_cad, "~> 0.2"}
  ]
end

This package is Elixir only, but ex_quiddity builds a Rust engine when it is compiled, so building wants a Rust toolchain and running wants none.

Limits, said out loud

  • A CAD frame is perfect: no sensor noise, no occlusion from a real probe. A library trained from one is a library trained from a drawing; docs/bridge.md says what that costs.
  • A library holds the part, densely and evenly. It does not hold an exploration policy — that is what a training episode is, and this package does not replace one.
  • The labels are exact because the recognition named the faces, not because anything was inferred. Where quiddity could not prove a feature, Quiddity.Recognition.unexplained_faces/1 is where that is visible.
  • A node whose patch saw too little surface is skipped and counted, not stored: an empty percept is not a node.

Licence

MPL-2.0 (LICENSE). The dependencies keep their own: ex_quiddity is MPL-2.0, ex_tbp_monty_core is MIT (the Thousand Brains Project's Monty, reimplemented on the BEAM), and the engine under ex_quiddity — quiddity and haecceity — is Apache-2.0. NOTICE says what came from where.