Search documentation
Search the Foldnano user guide, plugin docs and contributor docs

foldnano.edit API Reference

This package was previously called foldnano_mini, and the manifest format foldnano_edit was previously foldnano_mini too. The old names still work, so existing scripts and plugins keep running, but they are deprecated: use foldnano.edit and foldnano_edit in new code. Importing foldnano_mini logs a warning.

For how these fit together (loading a part, checking before you edit, crossovers, drawing paths), see Plugin Patterns. The same names, with signatures and docstrings, are in the type stubs and in Script Lab's autocomplete.

Every public name in foldnano.edit, with what it does (which package to import). Reads come from the in-worker mirror and always reflect earlier edits in the same run. Each edit records one or more ops in part.mutation_log (named in the Records notes) and updates the mirror at once. Where an edit has a can_* twin, the edit runs that check first and raises InvalidOperationError with its reason before recording anything (see Asking before you edit).

Kind says what each name does to the design:

  • read-only: a property you read; assigning to it is an error.
  • settable: a property you can assign to; the assignment records an op.
  • edit: a method that changes the design and records ops.
  • query: a method that only reads (lookups and can_* checks); nothing is recorded.
  • helper, function, constant, class: module-level names and utilities; they don't change the design.

Arguments named vh take a helix object or its index. strand_index is 0 (bases run 5' to 3' with increasing index) or 1 (decreasing). Bases (bp) are 0-based indices along the helix.

Module

APIKindBehaviour
load_part(json_str, active_region_id=None)functionParse a foldnano_json string (from ctx.get_part_as_foldnano_json()) into a MutablePart with an empty log. Pass ctx.active_region_id through.
KNOWN_OPSconstantThe set of op kinds the host can replay.
MutablePart, MutableOligo, MutableSegment, MutableSegmentEnd, MutableSlot, MutableVirtualHelix, MutableStrandSetclassThe editable proxies, below. You get them from load_part and from each other; don't construct them.
MutableRegion, LatticedRegion, FreeformRegion, WireframeRegion, MutablePlaneclassRegions and planes (below). Lattice and freeform regions are subclasses, and WireframeRegion is a FreeformRegion; test region.supports_lattice_grid rather than the class.
MutableRepeatGroup, MirrorOligoclassRepeat groups and their mirror oligos (below).
OligoView, SegmentView, SegmentEndView, SlotViewclassThe read-only side of the oligo proxies: everything you can read, nothing you can change. Mirror oligos are built from these.
MutationLog, Transport, OfflineTransportclassThe log and transport behind MutablePart. Plugins don't need them; part.mutation_log is the log to return.

MutablePart: contents and lookups

APIKindBehaviour
part.name, part.idread‑onlyThe part's name and id.
part.regions, part.virtual_helices, part.oligos, part.planesread‑onlyLists of proxies for everything in the part, current as of the last edit. part.oligos includes repeat groups' mirror oligos, as read-only MirrorOligos.
part.real_oligosread‑onlyEvery oligo except mirror oligos: the ones you can edit, and what the app's oligo list and exports show.
part.repeat_groupsread‑onlyThe part's repeat groups (MutableRepeatGroup).
part.active_region_id, part.active_regionread‑onlyThe region passed to load_part (id, and its proxy or None).
part.lattice_typeread‑onlyDeprecated, and logs a warning. The first region's kind only. Use region.kind.
part.max_baseread‑onlyDeprecated, and logs a warning. The first region's length only. Use region.max_base.
part.get_region(id), part.get_vh(index), part.get_oligo(id), part.get_repeat_group(id)queryThe proxy with that id or index, or None.
part.vhs_in_region(region_id)queryHelices in a region.
part.oligos_in_region(region_id)queryOligos whose segments all lie in that region (oligos crossing into another region are left out).
part.get_segment_at(vh, strand_index, bp)queryThe segment covering that base on that strand, or None.
part.get_oligo_at(vh, strand_index, bp)queryThe oligo covering that base, or None.
part.get_end_at(vh, strand_index, bp)queryThe 5' or 3' end sitting exactly at that base (free or connected), or None.
part.get_slot_at(vh, strand_index, bp)queryThe slot at that base of that strand (its sequence, skip or insertion), or None where no strand covers it.
part.potential_xover_sites(vh)query[strand 0 sites, strand 1 sites], as vh.potential_xover_sites.
part.mutation_logread‑onlyThe ops recorded so far, as JSON-ready dicts. Return it with ctx.set_result_mutation_log(...).
part.to_json(), part.to_json_string()queryThe whole mirror as a dict (a copy) or a JSON string, e.g. for ctx.set_result_foldnano_json(...).
MutablePart.replay_log_into_fresh_mirror(json_str, log)queryReplay a log onto a fresh copy of json_str and return the result; for testing that a log reproduces part.to_json().
part.allocate_vh_id(), allocate_oligo_id(), allocate_segment_id(), allocate_plane_id(), allocate_region_id()helperA new unique id. The edits call these themselves; you rarely need them.

MutablePart: edits

APIKindBehaviour
part.create_segment(vh, five_index, three_index, color="#888888", is_scaffold=False, name=None)editA new oligo with one segment. It runs on strand 0 when five_index < three_index, otherwise strand 1. Both ends must be inside the region's bases and the range free. Returns the segment. Records create_segment. Check: can_create_segment.
part.create_oligo(segments, color="#888888", name=None, is_scaffold=False, circular=False)editOne oligo from a path: segments lists (vh, five, three) tuples or SegmentSpecs from 5' to 3', and each segment's 3' end joins the next one's 5' end. Consecutive entries that continue each other on the same strand become one segment; circular=True also joins the last 3' end to the first 5' end. The whole path is checked first, so nothing is recorded if any part is refused. Returns the oligo; oligo.segments is in 5' to 3' order. Records create_segment per segment and connect per junction. Check: can_create_oligo.
part.fill_empty_strand_set(vh, strand_index, color="#888888")editOne segment along a whole empty strand, covering every base inside both the region and the helix, 5' to 3' in that strand's direction. Returns the segment. Records create_segment. Check: can_fill_empty_strand_set.
part.connect(seg_from, seg_to)editJoin seg_from's 3' end to seg_to's 5' end; both must be free and on different segments. If the ends are on the same helix and strand one base apart, the segments merge into one (seg_to is gone afterwards) instead of crossing over. If the segments were in different oligos, seg_from's oligo absorbs the other. Records connect. Check: can_connect.
part.connect_oligos(from_oligo, to_oligo)editJoin from_oligo's free 3' end to to_oligo's free 5' end, like dragging one end onto the other in the editor. The same oligo twice closes it into a ring. Adjacent ends merge as with connect. Clears modifications on the two joined ends. Records connect (and set_segment_modification when clearing). Check: can_connect_oligos.
part.create_crossover(from_oligo, vh, strand_index, bp)editLike dragging from_oligo's 3' end onto a strand: break the target strand just before bp and join the 3' end to it at bp. If the target oligo's free 5' end is at bp, it is joined without a break. Refused onto a 3' end, onto the base right after a free 5' end (it would leave a 1-nt piece), within one segment, or from a circular oligo. Records break_oligo and connect. Check: can_create_crossover.
part.make_crossover(vh, strand_index, bp, to_vh)editLike clicking a potential crossover site: (vh, strand_index, bp) and to_vh must name a site from vh.potential_xover_sites(strand_index). The strand whose 3' end leaves at the site is broken there and joined to the other helix's strand at the same base. Both strands must be present, and the receiving strand must not end at or 1 nt before its 3' end there. Records break_oligo and connect. Check: can_make_crossover.
part.break_at(vh, strand_index, bp)editThe break tool: break the strand at that base so its new 3' end is at bp. Returns the oligo holding the 3' side. Same behaviour as break_oligo. Check: can_break_at.
part.break_oligo(seg, at_index)editBreak seg's oligo so the new 3' end is at at_index, which must be inside the segment. Breaking at the oligo's own free 5'/3' end, or one base before a free 3' end, does nothing. Breaking a circular oligo opens it into one linear oligo (same id). Otherwise the 3' side becomes a new staple oligo (green, not scaffold), and a mid-segment break drops the segment's 3' modification. Refused if it would leave a 1-nt segment before a connected 3' end. Returns the oligo holding the 3' side. Records break_oligo. Check: can_break_oligo.
part.add_skip_at(vh, bp)editThe skip tool: skip that base on both strands. Refused at a segment end on both strands, at a crossover site, without a complementary strand, or where either strand already has a skip or insertion. Records add_skip_or_insertion. Check: can_add_skip_at.
part.add_insert_at(vh, bp, count=1)editThe insert tool: add count inserted bases at that base on both strands, keeping the existing bases. Calling it again adds more. Refused on a skip and in the places a skip is. Records add_skip_or_insertion. Check: can_add_insert_at.
part.remove_skip_or_insertion_at(vh, bp)editTurn a skip or insertion back into one base on both strands; nothing happens on a plain base. Records set_slot_skip.
part.remove_segment(seg)editRemove one segment. Segments before it stay in the oligo; those after it become a new oligo, which is returned (None if there were none). Removing an oligo's first segment removes that oligo id. Records remove_segment.
part.remove_oligo(oligo)editRemove the oligo and all its segments. Records remove_oligo.
part.remove_vhs([vh, ...])editRemove helices and everything on them; oligos passing through are split where they lose segments. Records remove_vhs.
part.reorder_vh(vh, target_region_row)editMove a helix to another row (0-based) within its region's list. Records reorder_vh.
part.add_region(kind, name, max_base, *, min_base=0, plane_origin=..., plane_u=..., plane_v=..., plane_normal=..., plane_name=None, step=None, turns_per_step=None, sub_step_size=None, twist_offset=None)editA new region ('HONEYCOMB', 'SQUARE' or 'FREEFORM') on its own new plane; lattice settings default per kind. The plane basis must be right-handed (plane_u × plane_v along plane_normal), or it raises ValueError. Returns the region. Records add_region_and_plane.
part.split_region(region, split_after_vh)editSplit a region after the given helix; the helices after it move to a new region with a copy of the plane. Returns the new region. Records split_region.
part.set_plane_transform(plane_id, origin, u, v, normal)editMove a plane: each vector is (x, y, z) or {x, y, z}. The basis must be right-handed (u × v along normal); a mirrored one raises ValueError, since its helices would twist the wrong way in 3D and export. Records transform_plane.
part.create_repeat_group(oligos, bp_offset)editMake oligos the originals of a new repeat group with one copy bp_offset bases along the same helices, like the editor's Duplicate as linked mirror copies. The copy's mirror oligos appear at once; a region the copy runs past grows. Returns the group. Records create_repeat_group. Check: can_create_repeat_group.

MutablePart: checks

Each returns a Check: truthy when the edit would be accepted, with .reason when not. They never record anything.

APIKindChecks
part.can_create_segment(vh, five_index, three_index)queryBoth ends inside the region's bases; the range free on its strand.
part.can_create_oligo(segments, circular=False)queryEvery segment as above, no two entries overlapping, no junction on a skip or insertion.
part.can_fill_empty_strand_set(vh, strand_index)queryThe strand is empty and the helix has room.
part.can_connect(seg_from, seg_to)queryDifferent segments, both ends free, neither end on a skip or insertion.
part.can_connect_oligos(from_oligo, to_oligo)queryA free 3' end and a free 5' end, not on one segment, not on a skip or insertion.
part.can_create_crossover(from_oligo, vh, strand_index, bp)queryThe rules listed for create_crossover.
part.can_make_crossover(vh, strand_index, bp, to_vh)queryThe rules listed for make_crossover.
part.can_add_skip_at(vh, bp), part.can_add_insert_at(vh, bp)queryThe rules listed for add_skip_at / add_insert_at.
part.can_break_at(vh, strand_index, bp), part.can_break_oligo(seg, at_index)queryA strand at that base; the base inside the segment; no 1-nt piece; a mid-segment break not leaving an end on a skip or insertion.
part.can_create_repeat_group(oligos, bp_offset)queryAt least one oligo, none a mirror or already in a group, and the copy placeable (see Repeat groups).

Each edit check above also refuses an edit to a mirror oligo, and an edit to a repeat group's original whose rebuilt copies wouldn't fit (the reason starts "Copy N of Repeat M").

MutableOligo

APIKindBehaviour
oligo.idread‑onlyStable id, shared with the app.
oligo.colorsettableHex color '#rrggbb'. Records set_oligo_color.
oligo.namesettableOptional free-form label, or None. Not unique and never used for identity (use oligo.id); it shows in the editor, the oligo report and the CSV Name column. Records set_oligo_name.
oligo.is_scaffoldsettableScaffold flag. Records set_oligo_is_scaffold.
oligo.is_sequence_lockedread‑onlyWhether the sequence is locked (read-only).
oligo.segmentsread‑onlyIts segments.
oligo.get_segment(segment_id)queryOne of its segments by id, or None.
oligo.is_circularread‑onlyTrue when it has no free ends.
oligo.five_prime, oligo.three_primeread‑onlyIts free 5' / 3' end (MutableSegmentEnd), or None when circular.
oligo.five_modification, oligo.three_modificationsettableThe modification on its free 5' / 3' end; None clears. Refused on a circular oligo. Records set_segment_modification.
oligo.touches_regions()queryThe set of region ids its segments lie in.
oligo.is_mirrorread‑onlyFalse for a MutableOligo (mirror oligos are MirrorOligos).
oligo.repeat_groupread‑onlyThe repeat group this oligo is an original of, or None.
oligo.remove()editSame as part.remove_oligo(oligo).

MutableSegment

APIKindBehaviour
seg.idread‑onlyStable id, shared with the app.
seg.oligo, seg.oligo_idread‑onlyIts oligo (looked up fresh, so still right after merges).
seg.in_virtual_helix, seg.vh_indexread‑onlyIts helix (proxy / index).
seg.in_strand_indexread‑only0 or 1.
seg.five_index, seg.three_indexread‑onlyBase of its 5' / 3' end.
seg.low_index, seg.high_indexread‑onlyIts base range regardless of direction.
seg.lengthread‑onlyNumber of bases.
seg.five_prime, seg.three_primeread‑onlyIts ends (MutableSegmentEnd).
seg.five_modification, seg.three_modificationsettableShorthand for seg.five_prime.modification / seg.three_prime.modification.
seg.connected_from_segment_id, seg.connected_to_segment_idread‑onlyId of the segment joined to its 5' / 3' end, or None.
seg.slotsread‑onlyIts stored slots: one per base for a segment loaded from the app, only the written ones for a segment made in this run.
seg.slot_at(bp)queryThe slot at a base of this segment (created on first write).
seg.move_five_prime_to(bp), seg.move_three_prime_to(bp)editSame as setting seg.five_prime.index / seg.three_prime.index.
seg.remove()editSame as part.remove_segment(seg).

MutableSegmentEnd

APIKindBehaviour
end.segment, end.is_three_primeread‑onlyThe segment it belongs to, and which end it is.
end.in_virtual_helix, end.in_strand_indexread‑onlyWhere it sits.
end.indexsettableIts base. Setting it moves the end: within the helix, not over another segment, without reversing the segment, not onto a skip or insertion; an end on a lattice crossover may only move to another matching site. Content outside the new range is dropped. Records move_terminus.
end.can_move_to(bp)queryThe check for setting end.index.
end.modificationsettableIts modification or None. Can be set on a free end only (None clears). Records set_segment_modification.
end.is_freeread‑onlyNot connected.
end.connected_segment, end.connected_endread‑onlyThe segment / end on the other side of its connection, or None.
end.is_constrainedread‑onlyConnected across a lattice crossover site: the two ends are on different strand indices at the same base, and the helix has a site there toward the other helix. Freeform connections never are.
end.is_adjacent_to(other_end)querySame helix and strand, one base apart (connecting them would merge the segments).

MutableSlot

APIKindBehaviour
slot.bp_indexread‑onlyIts base.
slot.seqsettableIts sequence: one base normally, empty for a skip, longer for an insertion. Settable with the same length only, so it can't make the two strands differ; change the length with part.add_insert_at / add_skip_at / remove_skip_or_insertion_at. Records set_slot_sequence.
slot.skipread‑onlyTrue when it is a skip.
slot.set_skip(True)editMake it a skip on both strands (same as part.add_skip_at, through this slot). Records set_slot_skip. Check: slot.can_set_skip().
slot.set_insertion(sequence)editReplace the whole slot with sequence (e.g. 'AT' is one inserted base); the complementary slot becomes that many Ns, losing its base. Prefer part.add_insert_at, which works like the editor. Records set_slot_insertion. Check: slot.can_set_insertion().
slot.can_set_skip(), slot.can_set_insertion()queryInside the segment, not at its end, the complementary strand present, not at a crossover site; a skip needs both strands plain, an insertion can't go on a skip.
slot.remove_skip_or_insertion(), slot.set_skip(False)editBack to a plain base on both strands; a plain slot is left alone. Records set_slot_skip.

MutableVirtualHelix

APIKindBehaviour
vh.index, vh.idread‑onlyIts index and stable id. A helix added in this run has a provisional index; use vh.id to refer to it reliably.
vh.region_idread‑onlyIts region's id.
vh.lengthread‑onlyNumber of bases, always the region's max_base. Setting it raises InvalidOperationError; use region.set_max_base.
vh.row, vh.colread‑onlyLattice position, or None for a freeform helix.
vh.is_freeformread‑onlyTrue when it has no lattice position.
vh.parity_is_even, vh.is_even_parityread‑onlyLattice parity (row % 2 == col % 2).
vh.local_framesettableA freeform helix's frame (origin, axis, radial0). The axis is normalized and radial0 made perpendicular to it; a radial0 parallel to the axis is refused. Records set_vh_local_frame.
vh.roll_degsettableA freeform helix's rotation about its own axis, in degrees (-180 to 180], or None for a lattice helix. 0 points radial0 along the region plane's normal (the plane's U axis for a helix standing along the normal); positive turns right-handed about the axis. Setting it keeps the origin and axis. Records set_vh_local_frame.
vh.move_to(col, row)editMove a lattice helix; refused if it would change parity. Records move_lattice_vh.
vh.strand_setsread‑only[strand 0, strand 1] as MutableStrandSets.
vh.segments_at(bp, strand_index=None)querySegments covering a base, on one strand or both.
vh.potential_xover_sites(strand_index)queryPotential crossover sites from that strand, as CrossoverSite(bp_index, to_vh_index, right); only sites whose neighbour position holds a helix in the same region. Freeform helices have none.
vh.strand_set_segment_idsread‑onlySegment ids on each strand, as stored.

MutableStrandSet

APIKindBehaviour
ss.strand_index, ss.virtual_helixread‑onlyWhich strand, and its helix.
ss.segments, ss.segment_idsread‑onlyIts segments, ordered by base.
ss.is_emptyread‑onlyNo segments.
ss.is_index_occupied(bp), ss.is_index_occupied_by(bp)queryWhether a segment covers that base / which one (or None).
ss.is_range_occupied(a, b), ss.segments_in_range(a, b)queryWhether any segment overlaps the inclusive range (either order) / which ones.
ss.free_ranges()queryFree (low, high) ranges within the region's bases: where a new segment fits.

Regions and planes

APIKindBehaviour
region.id, region.name, region.kind, region.plane_idread‑onlyIdentity, name, 'HONEYCOMB' / 'SQUARE' / 'FREEFORM' / 'WIREFRAME', and its plane.
region.step, region.min_base, region.max_base, region.default_max_baseread‑onlyLattice step (21 honeycomb, 32 square); its first base; one past its last base; the stored default length.
region.vh_order, region.virtual_helicesread‑onlyHelix ids in display order / its helices.
region.supports_lattice_gridread‑onlyTrue for honeycomb and square regions.
region.set_max_base(n)editLengthen or shorten every helix in the region so it ends at n. The change must be a multiple of step; shortening is refused while strands reach that far. Records set_region_max_base. Check: region.can_set_max_base(n).
region.can_set_max_base(n)queryWhether set_max_base(n) would be allowed, with the reason if not.
region.increase_length_by(delta)editLengthen by a positive multiple of step.
region.reduce_unused_length()editShorten to the strands it holds, rounded up to step and at least two steps, like the strand view's reduce button.
region.remove_all_skips(), region.remove_all_insertions()editTurn every skip / insertion in the region back into a plain base; returns how many.
lattice_region.add_vh_at(row, col, length=None)editA new helix at a lattice position (LatticedRegion). A length other than max_base raises InvalidOperationError. Records add_vh_at_coords.
freeform_region.add_vh_at(origin, direction=(0, 0, 1), length=None)editA new helix at a 3D position in nm (FreeformRegion), with roll 0. A length other than max_base raises InvalidOperationError. Records add_freeform_vh.
wireframe_region.reference_geometryread‑onlyThe ReferenceGeometry it was built from, or None. It is for reference: nothing keeps it in step with the helices.
wireframe_region.set_reference_geometry(geometry)editStore a ReferenceGeometry, or remove it with None. Records set_region_reference_geometry.
geometry.mesh, geometry.unitread‑onlyReferenceGeometry(mesh=None), imported from foldnano: its ReferenceMesh or None, and the unit, always 'nm'.
mesh.vertices, mesh.faces, mesh.edgesread‑onlyReferenceMesh(vertices, faces=(), edges=()), imported from foldnano: (x, y, z) positions in nm in the region's frame, faces of three or more vertex indices, and (a, b) vertex pairs. Bad values raise ValueError.
mesh.all_edges()queryEvery distinct edge as a sorted (a, b) pair: mesh.edges plus the sides of mesh.faces.
mesh.to_ply()queryThe mesh as ASCII PLY text.
plane.id, plane.name, plane.origin, plane.u, plane.v, plane.normalread‑onlyA plane's placement (read-only; move it with part.set_plane_transform).

Every virtual helix spans its region's max_base, in every kind of region. That is the space available, not the length of the DNA: strands can cover any part of it. For edges of different lengths, set the region's max_base to the longest edge and create strands only over the bases each edge needs. See Wireframe Plugins for worked examples.

Repeat groups

A repeat group copies its original oligos at each instance's offset along the same helices. The copies, mirror oligos, are rebuilt from their originals after every edit, here and in the app, and can't be edited themselves. Edit an original and its mirrors follow: color, name, scaffold flag, modifications and bases at once, anything else by rebuilding them. If a rebuilt copy wouldn't fit (it would overlap a strand, lose a lattice crossover site, or leave a skip or insertion unpaired), the edit raises InvalidOperationError and nothing is recorded.

APIKindBehaviour
group.id, group.nameread‑onlyStable id and display name (Repeat 1, Repeat 2, ...).
group.instancesread‑onlyIts copies, as RepeatInstance(id, bp_offset), in the order they are placed.
group.get_instance(id)queryOne instance by id, or None.
group.originalsread‑onlyThe oligos being repeated.
group.mirrors, group.mirrors_of(instance)read‑only / queryAll its mirror oligos, or one instance's.
group.add_instance(bp_offset)editAdd a copy at that offset (not 0 or one the group already has); its mirrors appear at once. Returns the RepeatInstance. Records add_repeat_instance. Check: group.can_add_instance(bp_offset).
group.can_add_instance(bp_offset)queryWhether add_instance(bp_offset) would be allowed, with the reason if not.
group.remove_instance(instance)editRemove one copy and its mirrors. Removing the last copy deletes the group. Records remove_repeat_instance.
group.detach()editDissolve the group, keeping its mirrors as ordinary, editable oligos. Records detach_repeat_group.
group.delete()editRemove the group and its mirrors; the originals stay as ordinary oligos. Records delete_repeat_group.
MirrorOligoclassA mirror oligo: the same reads as MutableOligo, with no setters or edits. Its segments, ends and slots are SegmentView, SegmentEndView and SlotView: read-only versions of the mutable proxies. Assigning to any of them raises InvalidOperationError.
mirror.is_mirrorread‑onlyTrue.
mirror.mirror_ofread‑onlyMirrorInfo(group, instance, source): what it copies.
mirror.sourceread‑onlyThe original it copies, as a MutableOligo.

Joining an ordinary oligo to an original (with connect or a crossover) pulls it into the group; both halves of a broken original stay in it; two groups can't be joined. A mirror's id is <source id>@<instance id> here, stable across rebuilds, but the app gives its mirrors its own ids, so don't keep a mirror id after the run. To edit every oligo, loop over part.real_oligos.

Values and errors

APIKindBehaviour
CheckclassResult of a can_* call: truthy when allowed; .ok, .reason, and .raise_if_failed().
InvalidOperationErrorclassRaised by an edit its check refuses (a ValueError). Nothing is recorded.
MIRROR_EDIT_ERRORconstantThe message an edit to a mirror oligo fails with.
RepeatInstance(id, bp_offset), MirrorInfo(group, instance, source)classA repeat group's copy, and what a mirror oligo copies.
SegmentSpec(vh, five, three)classA named (vh, five, three) entry for create_oligo.
CrossoverSite(bp_index, to_vh_index, right)classOne potential crossover site.

The proxies follow Foldnano's own model, so if you know Part, Oligo, OligoSegment, SegmentEnd and StrandSet, the names carry over in snake_case: seg.three_prime.connected_end, vh.strand_sets[0].is_range_occupied(10, 30).