gtrace.draw.viewer.editor

gtrace.draw.viewer.editor

A shape editor for Mechanics: the front end behind Mechanics.edit().

A body’s geometry is a list of drawing primitives in local coordinates, and until now the only way to lay one out was to write the numbers in a cell. That is fine for the shapes gtrace ships - a plate and a grid of holes - and tiring for anything a real bench has on it.

The editor is deliberately not a second viewer. It is the same one, handed a scene of nothing but the shapes being edited, drawn in the local frame with the origin marked: the origin is the point that comes to sit at the host’s substrate centre when the body is attached, so seeing it is most of what makes a part right. Zoom, pan, undo and the layer panel come along because they were never about optics in the first place.

What it edits is the Mechanics it was opened on - by reference, like everything else in gtrace - so a body already registered in a layout is redrawn there as soon as the layout is drawn again. Saving to the model library is one button, and the same register_model any cell would call.

Attributes

__author__

__copyright__

__credits__

__license__

__version__

__maintainer__

__email__

__status__

EDITABLE_SHAPE_ATTRS

DUPLICATE_OFFSET

EDIT_LAYER

_NOT_UNDOABLE

Classes

ShapeEditor

An editor for the shapes of one Mechanics.

Functions

_shape_points(s)

The points of a shape worth snapping to, as (point, kind) pairs.

_offset_shape_dict(d, by)

Move a serialized shape, for the copy a duplicate makes.

Module Contents

gtrace.draw.viewer.editor.__author__ = 'Yoichi Aso'
gtrace.draw.viewer.editor.__credits__ = ['Yoichi Aso']
gtrace.draw.viewer.editor.__license__ = 'BSD'
gtrace.draw.viewer.editor.__version__ = '0.8.0'
gtrace.draw.viewer.editor.__maintainer__ = 'Yoichi Aso'
gtrace.draw.viewer.editor.__email__ = 'asoy01@gmail.com'
gtrace.draw.viewer.editor.__status__ = 'Beta'
gtrace.draw.viewer.editor.EDITABLE_SHAPE_ATTRS
gtrace.draw.viewer.editor.DUPLICATE_OFFSET = 0.005
gtrace.draw.viewer.editor.EDIT_LAYER = 'shapes'
gtrace.draw.viewer.editor._NOT_UNDOABLE
class gtrace.draw.viewer.editor.ShapeEditor(mechanics)

Bases: object

An editor for the shapes of one Mechanics.

The body is held by reference - it is the user’s own object - so every edit lands on the body a layout may already be drawing, and the layout shows it at the next draw. What the editor does not touch is the pose: where a body stands is the layout’s business, and an editor that moved things while a shape was being drawn would be answering a question nobody asked.

Variables:

mechanics (gtrace.mechanics.Mechanics) – The body being edited.

mechanics
_history = []
_future = []
model_name
property shapes

the body’s own list.

Type:

The shapes being edited

draw(canvas=None, **options)

Draw the shapes into a canvas, in local coordinates.

Not through Mechanics.draw: that carries the shapes onto the bench through the pose, and an editor works in the frame the shapes are written in. The two agree whenever the body stands at the origin unturned, which is exactly the case this makes of every body.

scene_dict(**kwargs)

The scene an editing front end draws: the shapes, and the list of them it addresses by index.

The optics and beam channels are empty and stay that way. A part has no beams through it - it is a drawing of a body, and the bench it will stand on is elsewhere.

shapes_dict()

The shapes, each with the index an edit message names it by.

The index is the position in the list, which is also the order they are drawn in. It is not an identity: removing a shape renumbers the ones after it, exactly as a list does, and a front end holding one across an edit is holding a place rather than a thing.

points_dict()

The points the part names for itself, each with the index an edit message names it by.

A list, where the body keeps a dict: a panel needs a row to stay where it is while its name is being typed, and a name that is being typed is halfway to something else. So the index is the place in the list and the name is a value like any other - which is what lets one be renamed at all.

snap_points()

Points worth snapping to while drawing: the origin, and the corners, centres and edge midpoints of the shapes already down.

The origin is first because it is the one point every part is drawn around - it becomes the host’s substrate centre - and the one a new shape most often wants to line up with.

apply_edit(msg)

Apply an edit message from an editing front end:

{'op': 'add_shape',       'type': 'circle'}
{'op': 'set_shape',       'index': 2,
                          'attrs': {'radius': 0.004}}
{'op': 'remove_shape',    'index': 2}
{'op': 'duplicate_shape', 'index': 2}
{'op': 'move_shape',      'index': 2, 'to': 0}
{'op': 'rotate_shape',    'index': 2, 'angle': 0.7854,
                          'pivot': [0.0, 0.0]}
{'op': 'set_points',      'points': [{'name': 'post',
                                      'point': [-0.0135, 0.0]}]}
{'op': 'save_model',      'name': 'MY-PART',
                          'description': 'one line'}
{'op': 'undo'}
{'op': 'redo'}

A shape is edited by taking it apart into the dict shape_to_dict writes, changing what the message names, and building it again - so a value that describes no shape is refused by the constructor, and the shape it would have replaced is left alone.

A turn is the one edit that is not a set of attributes: what turning means differs by kind - an arc’s two angles move, a text turns with its own rotation, and a rectangle carries a turn of its own, so it takes the angle and the point it was taken about and stays a rectangle. The angle is in radians, counterclockwise; pivot defaults to the middle of the shape’s bounding box, which is the box a front end draws around it.

The named points are set as a whole list rather than one at a time. There is no index that survives a rename - a point is known by its name, and a name is the thing being edited - so every gesture that touches them, adding one, moving one, renaming one, taking one away, arrives as the list they leave behind. One message is also one step of undo, which is what a drag of a point should be.

Raises:

EditError – If the operation, the index or an attribute is not allowed.

_apply_edit(msg)
_index(i)

A shape index from a message, or an EditError.

_points(entries)

The named points of a part from a message, or an EditError.

Built whole before anything is put in place, so a list with one bad entry in it leaves the part with the points it had.

_build(d)

A shape from its serialized form, with a refusal a front end can show. This is where a width of ‘wide’ or a circle of no radius is turned away.

The constructors carry most of it - a polyline whose x and y no longer match says so itself - and what they do not is the arithmetic that has to hold for a shape to be drawable at all: a size is positive and every number is finite. Both are checked on the way out, against the shape as it came back, rather than against the message.

_snapshot()
_restore(snapshot)
_fill_points(points)

Put these named points in the body’s own dict, in this order.

undo()

Put the shapes back as they were before the last edit.

redo()

Put back the state the last undo stepped out of.

property can_undo
property can_redo
widget(height=None, **kwargs)

Return a Jupyter widget for editing this part.

The same viewer the layout uses, handed a scene of nothing but the shapes: zoom, pan, undo and the layer panel are the same code, and what differs is the side bar, which offers shapes instead of optics.

Requires anywidget, like any other notebook viewer.

gtrace.draw.viewer.editor._shape_points(s)

The points of a shape worth snapping to, as (point, kind) pairs.

The middle of every straight edge is among them, alongside the corners and the ends. It is not one of the shape’s own numbers - no row of a panel sets it - but it is a place a part is drawn against: a plate centred on the edge of another, a hole on the middle of a side. A curve has none; the middle of an arc is not a place anything is lined up on.

gtrace.draw.viewer.editor._offset_shape_dict(d, by)

Move a serialized shape, for the copy a duplicate makes.