geop-core-editor
What editing is. The vocabulary a step’s editing is said in — dialogs,
visuals, gizmos, events, picks (editing) — numbers typed as formulas
(EditForm::numeric), what an operation shows and does with them
(editable, part_operation), how a reference field picks an element
(selectable), and the operations an editor offers (Editors). What an
editor draws of a part and picks in it is the view, and what is made of a
part rather than in it — files and answers — the output. The interaction
every operation shares, driving these, is one step edited
(step_editor), and the whole of editing a program is
the editor (editor), over what a
registry offers: it names no operation, so an
operation crate can drive an editor of three operations in its own tests.
A program runs without any of it (see
geop-core-program).
An operation (PartOperation)
Every operation of a part implements PartOperation and is made an
Operation and an Editable by part_operation!(MyOp(MyOpArgs)):
KIND, the name its steps are stored under, andINFO, anOperationInfo— its label, a sentence of what it does, itsOperationGroup(the toolbar’s section) andOperationTier(how prominently it is offered there), its icon, the file extensions it reads beside programs (reads, a STEP file to import) and whether its picks are made in what it builds rather than the part before it (picks_built: a placed part’s mates), or in the whole part, the steps after it built too (picks_whole: the properties’ colours of faces).Args, the arguments a step is given and saved with: numbers, choices, sketches, and references to elements by name.new_args(context): the arguments of a new step, from what the part before it holds — an extrude takes the newest sketch.run(step, runner, part, args): what it builds, and the runner running it, to run programs of its own. A step that builds into the part’s model is aModelOperationof geop-ops-model and runsbuild_model.form(context, args, editing): the one form it is edited through, anEditForm(below). It never fails: an editor needs a dialog most exactly when the arguments do not build, so whatever is wrong is said in it.Session: what it keeps between events besides its arguments (()for most; the tool in hand and a half-drawn curve for a sketch), what is forgotten before each event (begin_event), andcanvas, what it does with the pointer and keys beyond its fields — clicks while it has a tool in hand, and those on nothing, hovers, keys, moves of its draggable visuals.
Everything is edited against a Context: the step’s id, the runner it is
run by (runner, which reads the files around its program — a picker of
files, a placed program’s parameters), the part as it was
before the step (before, a Reader), the part as the step built it when
it last ran (built), and, for an event, the part as the editor draws it
(scene), so an operation picks in the editor’s drawing rather than
drawing the part again for every move of the pointer.
One form, with its setters
The edit_form module. An EditForm is a Form built together with what setting each of its
fields does. Each field is added once, with its setter — number,
numeric, checkbox, select, actions, file, color, and pick /
pick_one for reference fields — and an event is answered by the setter of
the field it sets (EditForm::set), never by matching keys. A setter gets
the whole Edit: the step’s arguments and its session. It may capture what
the form was built from, for what setting one field implies for others:
another sketch picked brings its own axis, a selection picks the
construction it fits. Sub-forms shared by several operations add fields
with setters to an EditForm in the same way. An operation only ever
hands out the form (EditForm::into_form); the setters stay its own,
built again for each event.
What is shown and what reaches it
The editing module.
- A
Formis plain data: aDialog— an ordered list of keyed fields that say what their values mean: headings and texts, actions (things to do or choose, grouped, one perhaps active, a disabled one saying why), checkboxes, numbers (with aUnit, a slider range and perhaps a handle to drag them by, aTrack), selects, lists, references to elements of the part, a file to download (Dialog::download) — andVisuals to draw in the viewport: points, polylines, filled areas and labels, each perhaps selectable or draggable. Besides, a plane to work in (focus), a sheet of its own (a drawing’s paper), what it has in hand (InHand), the visuals selected, a value asked for in place (Prompt), aGizmo, andpulls: where it wants the state pulled (Pull::Pointa drag,Pull::Posea placement typed in,Pull::Valuea joint’s coordinate set). An operation never sets the state itself; the editor solves the program with its pulls, and the constraints have the last word. - An
Eventis what reaches an operation once the editor has done what it does alike for every operation: a field set (Event::Field), the selection now (Event::Select), or what the pointer or a key did beyond that (Event::Canvas, aCanvasEvent). How a field was set — typed, picked for in the viewport, dragged by its handle — the operation never knows. - A
StepEditEventis what the user did, as a front end sends it: a dialog field used, a hover, a click or a drag in the viewport, each aPointer— theRayfrom the eye through the cursor and itsReach, how far from the ray counts as under it (a cone from the eye in perspective, a tube in an orthographic view) — or a key. - A
Presentationis what an editor shows of a step: the form, what the reference fields hold and what a click would pick lit, what is selected and hovered, the handles, and what a click picks now (grab). - A
Gizmo(editing::gizmo) is the one way every operation offers a drag in three dimensions: arrows, planes and rings to move along, in and about its axes, and cubes to scale, laid out in the pointer’s reaches so it keeps its size on screen. The editor draws it, follows a drag and snaps it, and sends what the drag did as aGizmoDrag, measured from where the drag started. - Hit tests (
editing::hit): which visual a pointer is over (hit_visuals), and theScenean editor picks elements in. Near means within the pointer’s reach, so it means near on screen however far away or zoomed in the view is; the ray geometry itself isgeop_core_math::primitives::Ray’s.
So an editor renders primitives and forwards raw input, and every decision — what a click picks, what a line snaps to, what a drag changes — is made in Rust, where it can be tested.
Picking references
The selectable module. A reference field holds Selectable values, one per pick. An
ElementReference<T, K> is one when its kind is a SelectableElement:
the kind says what a click on what the editor draws selects
(PICKED_FROM, the drawn kinds it takes a click on, and select) and what
of it to light while it is held (drawn). What an operation asks for may
be something else drawn as those: a solid picked by a face of it, a tangent
chain by an edge of it, a line by a straight edge. The crate defining the
kind says how; the editor stays the same for all of them.
Where an operation takes the same thing from several sources — a line from
a straight edge, a datum axis or a sketch line — its argument is an enum
declared with one_of!, each variant an element reference, all resolving
to one value:
one_of! {
/// What a revolve turns about.
pub enum AxisRef -> Axis<Design> {
Edge(ElementReference<NamedModel<Design>, Line>),
Datum(ElementReference<Datums, Line>),
}
}
// args.axis.resolve(&reader)? -> Axis<Design>
// saved as {"edge": "extrude(box,K,c3)"} or {"datum": "origin:z"}
A click is tried against the variants in order. selected gives the
values a field holds after the editor sent it its picks — a click on a
value held already takes it out again — and selectable::pick picks
directly on the canvas, for a step that picks without a reference field.
Numbers as formulas
A NumericInput (geop-core-program)
is a number an operation is given, typed as a plain value or as a formula
of the program’s parameters. An operation’s arguments take one wherever
they take a number, and its form shows it with EditForm::numeric: a
field of Number::numeric, showing what it comes to as the part before
the step reads the parameters — or, where it does not evaluate, why —
with no slider and no handle while it follows a formula. Text typed sets
it, plain if it is a number and a formula otherwise, and a number slid or
dragged sets it plain (Value::numeric_input).
Operation sets
Editors is the operations an editor offers, by kind (with): what gives
each step its arguments, form and events, and — through
Editors::operations — the OperationRegistry its programs run with, so
the two cannot disagree. infos lists them in the order they were added,
each a Listed (kind and OperationInfo), which is what a front end
builds its toolbar from. An editor’s are its
registry’s.
The view (view)
PartView: a part as the viewport draws it, and picking elements of it
with a Pointer.
Drawing
A part is drawn from its rasterization — sampled edges, triangulated faces,
outlined sketches (see geop-core-rasterize) —
and a pick tests against exactly that, never re-deriving geometry of its
own, so a pick cannot disagree with what is on screen. The same goes for
what the viewer draws besides the part: the datums are laid out here, by
the sizes the viewer draws them with (a frame’s axes are FRAME reaches
long), and picked by the same rules. Serialized, a PartView is what a
viewer draws, with the targets its model, datums and sketches are of: a
viewer lights an element the editor names (a PickedElement) by the
target, kind and name it carries, as a pick names it.
Each crate draws its own targets into a view — the model its vertices,
edges and faces, the sketches theirs, the datums theirs, the placed parts
theirs — by a Drawer of its registry, and the registry composes them
(Registry::draw: every drawer in turn, then what the steps annotate),
then PartView::finish measures the result. What each drawer drew is kept
by what it read (Drawings, Reader::recorded): the part shown again
with other sketches — a sketch opened, or closed — draws its sketches
again, never its faces. A face’s triangles are one FaceMesh, known by a
hash of what a viewer is sent of it.
The parts placed in a part are drawn by reference: each placed part —
however deep — is listed once, with where it is (ViewInstance), and drawn
from its component’s own view, built once and shared by every instance of
it. Moving a placed part changes where it is, not what is drawn.
Picking
PartView::pick(pointer, accepts) finds what the pointer is over among
what is drawn of the kinds a field accepts (drawn: vertex, edge,
face, sketch, sketch_point, sketch_curve, datum, frame). Near
means within the pointer’s reach. Frame datums are drawn on top of
everything, so they are picked first; otherwise the smallest element
under the pointer and not hidden behind a face wins — a vertex or a sketch
point over an edge or a sketch curve — and else the nearest along the ray
of the faces, sketches and other datums hit. A pick names what it hits as the crate that drew it
named it, a PickedElement, which the crate defining the element turns
into a typed reference (see
Picking references).
A pick moves the pointer into each placed part’s own frame and names what
it hits as that part names it, its target behind the instance’s name
(Located: /bolt:model). pick_instance and part_to_drag find the
placed part under the pointer, for dragging it. A frame pick (a
FramePick) puts a frame on what is picked — a face, an edge, a point, a
datum — at the corner or side of a face, or the end of an edge, that the
pointer is near: what a joint is made between (see
geop-ops-assembly).
What steps add to the drawing and the structure
An editor knows no operation by name. What a step adds besides its topology, it adds as a target of its own, which the editor collects without knowing whose it is:
- Annotations (
annotations(step), anAnnotations): what a step draws on the part besides its topology — a cosmetic thread’s helix and designation (geop_ops_model::draw_threadsis the example).annotations_ofcollects every step’s. - Structure entries (
listing(step), aListingofListed): what a step lists in the part’s structure tree beside what every part has (datums, sketches, solids, faces standing on their own, placed parts): a kind (mate, the tree’s group), a name and the elements it refers to, lit while it is pointed at.listed_by_stepscollects every step’s;add_partlists its mates this way.
icons holds the icons operations share.
Outputs (output)
What is made of a part rather than in it: files written from what a
program built (Exporter), and questions answered about it (Inspector).
Neither is a step: exporting and inspecting change no program and are
nothing to undo. A crate that writes a format, or answers a question,
implements one of the traits on a unit struct, as an operation does; an
editor offers those registered in its Outputs, by their infos, and knows
nothing more of them. The options each takes are its own, sent as JSON.
What they are given (Output)
An Output is what a program built, as an exporter or an inspector is
given it: the runner holding the part as far as the program runs, the parts
it places included (part()), the part before a step (part_before, what a
drawing step is of), the program file it ran (file_name), with its state —
to ask how its parts can move by running it again — the step being edited
and the date. What the program says of its part (its title, designation,
colour and material) is read from the part like any element of it
(geop_ops_model::parameters::read_properties, see
geop-ops-model), and a
file written from it is named after the program’s file
(geop_ops_model::parameters::stem(output.file_name())).
Exporters
An Exporter is a kind of file written from what a program built. Its
INFO (ExporterInfo) gives its kind, label, a few words of what the file
is for, and the formats it writes by extension, the default first; its own
Options are deserialized from the JSON a front end sends (Default where
it sends none; NoOptions for one that takes none); and
export(output, format, options) returns an Export — a name, a media
type, and text or bytes (Content). A step’s dialog offers a file of its
own with Dialog::download(key, exporter, formats).
The STL exporter (geop_ops_assembly::Stl) writes the mesh the editor
draws; others write STEP files, drawings, URDF robots, flat patterns and
bills of materials, each in its own crate.
Inspectors
An Inspector is a question answered about what a program built. Its
INFO (InspectorInfo) says whether it is asked of the whole part or of
picks (Picks: the drawn kinds it accepts, at most how many);
inspect(output, picked, options) returns its Report (any Serialize)
and the visuals to draw of it in the viewport, and show(report) lays the
report out for a front end as an Answer — text, lists, tables, groups,
buttons that ask again or save a file (Block, AnswerAction), and a
plane to cut a section with — so a front end shows every inspector’s answer
alike, knowing none. An inspector that picks becomes the editor’s pick
tool, asked again with every pick. Bounded is a number as a report shows
it: the midpoint of an enclosure and its half-width.
Registering them
Exporters and inspectors are registered in an Outputs (exporter,
inspector; a kind twice is refused), which a
registry holds beside its operations.
The registry (registry)
Everything an editor offers is a Registry’s, one builder every operation
crate adds to with its one register(r: Registry) -> GeopResult<Registry>;
an application calls each crate’s in turn (the editor’s is
geop-cad-base’s registry). Each way of adding
something is a trait:
- Operations (
operation, anEditable): offered within their group and tier in the order they were added;operations()gives the runner the same set. Drawer: draws the targets a crate defines into the part’s view (draw), lists them in the structure tree (structure, each aStructureItemwith thekind_labela front end groups it under, shown unless hidden) and says what it draws only to be picked from (hidden: the datums of placed parts, shown while a part is placed). A part is drawn by every drawer in turn (Registry::draw) and listed likewise (Registry::structure, then what the steps list).- Exporters and inspectors (
exporter,inspector), as above. Panel: program-level state a front end shows beside the steps, under itsKEYinProgramState::panels(state), the commands it takes asCommand::Panel { key, payload }(command, its ownCommandtype), and what it asks once an edit (after_edit): the parameters, the joints of an assembly.Tool: a mode in hand while no step is edited, taken in hand by its key (Command::Tool), offered as itsToolInfosays (ProgramState::tools); the events of the viewport are its (handle), it shows what it would take (presentation), and may answer about the part with every update (inspection). The pick tool (tools::pick::PickTool) is the core’s, one per inspector that picks, by the keypick:<inspector>.Settle(at most one): the solve the editor runs after every edit, moving the program’s state so the constraints its steps declare hold (settle, given what the step edited pulls), and naming the state it solves for (solvable). With none, the state is kept as given.ExampleSet: programs and workspaces of several filesCommand::LoadExampleandLoadWorkspaceExampleload, listed by name without being made.
A panel, a tool or a settler sees the editor through an EditorContext
(the program, the part as it last ran, the part as drawn, running it all
again, Rerun, moving the state, editing the program with the marker and
the step edited kept at their steps, one undo for a whole drag), so undo
and the marker stay right whatever an extension does.
The editor
Editor (editor) is the whole of editing a program, as one state
machine: it holds the program, its undo and redo, how far it runs (the
marker), and the step being edited — a StepEditor (below) and the part
before the step, as drawn, for picking. An editor drives it with
Commands and draws the Update each answers (handle, or handle_json
on the wire), so it keeps no state of its own beyond the camera. The
program is the one of a file, among others it may place parts from: the
editor is told the file’s path with the program (Command::Load), and is
sent the other files’ programs whenever they change (Command::Files) by
the front end, which owns the files. It is made of a registry and a
FileStore the files it is sent are kept in (Editor::new).
- Program commands:
new(a step of an operation, inserted where the program runs to),open(a step, to edit),remove,move,copy,paste,seek,load,load_example,undo,redo. Each is refused, changing nothing, while a step is being edited, or when it would leave the program invalid; an update says why. - Step commands:
event(what the user did to the step),preview(show the part the step builds, or the part before it),commit(put the step into the program — only if it builds) andcancel. - Extensions:
panel(a command to a panel, by key) andtool(take a tool in hand by its key, or put it down: with no step edited, the viewport’s events are the tool’s). - Output commands, which change nothing:
export(write the part with an exporter, by its kind, in a format, told its own options: the update’sexport) andinspect(ask an inspector about the part: the update’sinspection,{kind, report, shown}: the answer as data, and as the inspector lays it out). The program state lists theexportersandinspectorsoffered, so a front end’s menus are made from them.
After every command that changed something the editor settles the state
(the registry’s Settle: while a step that pulls nothing is edited, only
the program up to it runs; once the whole program has run, state nothing
solves for goes), runs what is shown, and tells the panels once an edit.
An update holds the program as a list of steps shows it — each step’s
fields in one line and whether it failed — the panels’ states, the part to
draw, and the step being edited with its presentation and whether it
builds. What did not change since the last update is left out: a hover
sends only the step, and the part is sent again only once something ran.
The steps are run by one Runner — up to and including the step being
edited while there is one, else as far as the program runs — on one part,
a memo table of every step’s nodes, so the part before the step being
edited is always at hand, and a change to it makes again only what reads
what changed. The builds of the last few files edited are kept, so opening
one again builds nothing. The parts placed in the part drawn are sent as
what changed since the last scene: their components once each, by key.
So are the faces’ meshes, of the part and of the components: a face is
sent by its mesh’s key, each mesh once, and the viewer forgets one when
told to (forgotten_meshes), once nothing drawn has used it for longest.
What the shown steps built on — their reference fields’ values — is hidden
from the drawing: sketches and whole datums, since what was made from them
shows them now; none that something of could be picked while a field waits
for a pick. Every scene lists the part’s structure (Registry::structure);
Command::Visibility shows or hides one entry by name over what the editor
would by itself, for as long as the same file is edited.
testing (feature testing) drives an editor as a front end does:
editor_with(registry, program), Stepping (one step edited, gesture by
gesture), pointers, clicks and dialog fields.
Editing one step
How a field is edited is the same for every operation, so it is not the
operations’ but the StepEditor’s (step_editor), which edits one step.
It turns what the user did — a StepEditEvent (see
What is shown and what reaches it)
— into what the operation understands, an Event:
- Reference fields hold
Selectablevalues: elements of the part of the kinds the field takes. A field arms on a press; the editor then finds what a click would pick on hover, lights it, and picks it on a click — once for a field of one element, again and again (each pick adding one or taking it out) for a field of several. Taking one out, or all, is the editor’s too, as is saying what each element held is used as, or why it cannot be. The operation is only ever sent what the field holds now. A new step starts with its first reference field still waiting armed: what it is built on is what it needs first. A single pick arms the next one waiting, as a new revolve’s axis waits once it has its sketch. One that already holds something — an extrude’s newest sketch — waits for no click, so its handles can be dragged straight away. So does a field that appears empty as the step is edited — a mate just added waits for its entities — and a field that goes away stops waiting. While a field waits for a pick, the part is shown as it stands, not the plane the step works in. Picks test against the part before the step, or — for an operation whose references point into what it adds itself (OperationInfo::picks_built), a placed part’s mates — against what it builds; for one saying something of the finished part and reading none of it (OperationInfo::picks_whole), against the whole part, kept built while the step is edited. - Handles of number fields are drawn by the editor and dragged along
their direction, the value following how far the pointer has moved along
the handle’s track, from the ray where the drag started to the ray where
it is now, snapped to a hundredth (
DRAG_SNAP). - Selecting is the editor’s: a click on a selectable visual selects it or takes it out again, a click on nothing clears the selection unless shift is held, and Escape clears it. The operation reads the selection in its form and may change it as its fields are used. The editor draws what is selected and what the pointer is over.
- Dragging a draggable visual is the editor’s too: it is grabbed where the drag starts, and the operation is sent where to, in the plane worked in — or, with none, in the plane through where it was grabbed, facing the eye. With a tool in hand that strokes, a drag from anywhere else is the tool’s.
- The gizmo, if the form has one, is drawn over everything and grabbed first; a drag of one of its parts is followed, snapped and sent to the step as it was when it was pressed, so each event applies the whole drag once.
- Keys: Escape, Enter, Delete and a secondary click mean the same in
every tool (
CanvasEvent::Cancel,Confirm,Delete); any other key is a tool’s shortcut.
Whatever else the pointer and the keys do goes to the operation as a
CanvasEvent: clicks while it has a tool in hand, and those on nothing,
hovers, keys. Hit tests against visuals (hit::hit_visuals) and against
the part as drawn (PartView::pick, through the context’s Scene) decide
what a pointer is over, so a pick cannot disagree with what is on screen.