Browse Source

made structure part of element and link loci

Alexander Rose 6 years ago
parent
commit
ec99c5f246

+ 4 - 4
src/mol-model/shape/shape.ts

@@ -55,14 +55,14 @@ export namespace Shape {
 
     export interface Loci {
         readonly kind: 'group-loci',
+        readonly shape: Shape,
         readonly groups: ReadonlyArray<{
-            shape: Shape,
             ids: OrderedSet<number>
         }>
     }
 
-    export function Loci(groups: ArrayLike<{ shape: Shape, ids: OrderedSet<number> }>): Loci {
-        return { kind: 'group-loci', groups: groups as Loci['groups'] };
+    export function Loci(shape: Shape, groups: ArrayLike<{ ids: OrderedSet<number> }>): Loci {
+        return { kind: 'group-loci', shape, groups: groups as Loci['groups'] };
     }
 
     export function isLoci(x: any): x is Loci {
@@ -70,11 +70,11 @@ export namespace Shape {
     }
 
     export function areLociEqual(a: Loci, b: Loci) {
+        if (a.shape !== b.shape) return false
         if (a.groups.length !== b.groups.length) return false
         for (let i = 0, il = a.groups.length; i < il; ++i) {
             const groupA = a.groups[i]
             const groupB = b.groups[i]
-            if (groupA.shape.id !== groupB.shape.id) return false
             if (!OrderedSet.areEqual(groupA.ids, groupB.ids)) return false
         }
         return true

+ 1 - 1
src/mol-model/structure/query/selection.ts

@@ -50,7 +50,7 @@ namespace StructureSelection {
             }
         }
 
-        return StructureElement.Loci(loci);
+        return StructureElement.Loci(sel.source, loci);
     }
 
     export interface Builder {

+ 4 - 2
src/mol-model/structure/structure/element.ts

@@ -8,6 +8,7 @@ import { OrderedSet, SortedArray } from 'mol-data/int'
 import Unit from './unit'
 import { ElementIndex } from '../model';
 import { ResidueIndex, ChainIndex } from '../model/indexing';
+import Structure from './structure';
 
 interface StructureElement<U = Unit> {
     readonly kind: 'element-location',
@@ -44,6 +45,7 @@ namespace StructureElement {
     /** Represents multiple element index locations */
     export interface Loci {
         readonly kind: 'element-loci',
+        readonly structure: Structure,
         /** Access i-th element as unit.elements[indices[i]] */
         readonly elements: ReadonlyArray<{
             unit: Unit,
@@ -55,8 +57,8 @@ namespace StructureElement {
         }>
     }
 
-    export function Loci(elements: ArrayLike<{ unit: Unit, indices: OrderedSet<UnitIndex> }>): Loci {
-        return { kind: 'element-loci', elements: elements as Loci['elements'] };
+    export function Loci(structure: Structure, elements: ArrayLike<{ unit: Unit, indices: OrderedSet<UnitIndex> }>): Loci {
+        return { kind: 'element-loci', structure, elements: elements as Loci['elements'] };
     }
 
     export function isLoci(x: any): x is Loci {

+ 3 - 2
src/mol-model/structure/structure/unit/links.ts

@@ -41,11 +41,12 @@ namespace Link {
 
     export interface Loci {
         readonly kind: 'link-loci',
+        readonly structure: Structure
         readonly links: ReadonlyArray<Location>
     }
 
-    export function Loci(links: ArrayLike<Location>): Loci {
-        return { kind: 'link-loci', links: links as Loci['links'] };
+    export function Loci(structure: Structure, links: ArrayLike<Location>): Loci {
+        return { kind: 'link-loci', structure, links: links as Loci['links'] };
     }
 
     export function isLoci(x: any): x is Loci {

+ 1 - 1
src/mol-repr/shape/index.ts

@@ -69,7 +69,7 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
         getLoci(pickingId: PickingId) {
             const { objectId, groupId } = pickingId
             if (_renderObject && _renderObject.id === objectId) {
-                return Shape.Loci([ { shape: _shape, ids: OrderedSet.ofSingleton(groupId) } ])
+                return Shape.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId) }])
             }
             return EmptyLoci
         },

+ 4 - 4
src/mol-repr/structure/units-visual.ts

@@ -50,8 +50,8 @@ interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
     defaultProps: P
     createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: P, geometry?: G): Promise<G>
     createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
-    getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
-    mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
+    getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
+    mark(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean): boolean
     setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
 }
 
@@ -168,7 +168,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
             }
         },
         getLoci(pickingId: PickingId) {
-            return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
+            return renderObject ? getLoci(pickingId, { structure: currentStructure, group: currentGroup }, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
             if (!renderObject) return false
@@ -185,7 +185,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
             if (isEveryLoci(loci)) {
                 changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
             } else {
-                changed = mark(loci, currentGroup, apply)
+                changed = mark(loci, { structure: currentStructure, group: currentGroup }, apply)
             }
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)

+ 1 - 1
src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts

@@ -114,7 +114,7 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
         const indexA = OrderedSet.indexOf(carbA.unit.elements, carbA.anomericCarbon)
         const indexB = OrderedSet.indexOf(carbB.unit.elements, carbB.anomericCarbon)
         if (indexA !== -1 && indexB !== -1) {
-            return Link.Loci([
+            return Link.Loci(structure, [
                 Link.Location(
                     carbA.unit, indexA as StructureElement.UnitIndex,
                     carbB.unit, indexB as StructureElement.UnitIndex

+ 1 - 1
src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts

@@ -193,7 +193,7 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num
         const index = OrderedSet.indexOf(unit.elements, carb.anomericCarbon)
         if (index !== -1) {
             const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
-            return StructureElement.Loci([{ unit, indices }])
+            return StructureElement.Loci(structure, [{ unit, indices }])
         }
     }
     return EmptyLoci

+ 1 - 1
src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts

@@ -94,7 +94,7 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
     if (id === objectId) {
         const pair = structure.crossLinkRestraints.pairs[groupId]
         if (pair) {
-            return Link.Loci([ Link.Location(pair.unitA, pair.indexA, pair.unitB, pair.indexB) ])
+            return Link.Loci(structure, [ Link.Location(pair.unitA, pair.indexA, pair.unitB, pair.indexB) ])
         }
     }
     return EmptyLoci

+ 6 - 7
src/mol-repr/structure/visual/inter-unit-link-cylinder.ts

@@ -73,7 +73,7 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
     const { objectId, groupId } = pickingId
     if (id === objectId) {
         const bond = structure.links.bonds[groupId]
-        return Link.Loci([
+        return Link.Loci(structure, [
             Link.Location(
                 bond.unitA, bond.indexA as StructureElement.UnitIndex,
                 bond.unitB, bond.indexB as StructureElement.UnitIndex
@@ -85,12 +85,11 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
 
 function markLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
     let changed = false
-    if (Link.isLoci(loci)) {
-        for (const b of loci.links) {
-            const idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
-            if (idx !== -1) {
-                if (apply(Interval.ofSingleton(idx))) changed = true
-            }
+    if (!Link.isLoci(loci)) return false
+    for (const b of loci.links) {
+        const idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
+        if (idx !== -1) {
+            if (apply(Interval.ofSingleton(idx))) changed = true
         }
     }
     return changed

+ 26 - 22
src/mol-repr/structure/visual/intra-unit-link-cylinder.ts

@@ -11,7 +11,7 @@ import { VisualUpdateState } from '../../util';
 import { LinkCylinderProps, createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
-import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
+import { UnitsMeshVisual, UnitsMeshParams, StructureGroup } from '../units-visual';
 import { Interval } from 'mol-data/int';
 import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
@@ -87,33 +87,37 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
     })
 }
 
-function getLinkLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+function getLinkLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
-    const unit = group.units[instanceId]
-    if (id === objectId && Unit.isAtomic(unit)) {
-        return Link.Loci([
-            Link.Location(
-                unit, unit.links.a[groupId] as StructureElement.UnitIndex,
-                unit, unit.links.b[groupId] as StructureElement.UnitIndex
-            )
-        ])
+    if (id === objectId) {
+        const { structure, group } = structureGroup
+        const unit = group.units[instanceId]
+        if (Unit.isAtomic(unit)) {
+            return Link.Loci(structure, [
+                Link.Location(
+                    unit, unit.links.a[groupId] as StructureElement.UnitIndex,
+                    unit, unit.links.b[groupId] as StructureElement.UnitIndex
+                )
+            ])
+        }
     }
     return EmptyLoci
 }
 
-function markLink(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
-    const unit = group.units[0]
-
+function markLink(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
     let changed = false
-    if (Unit.isAtomic(unit) && Link.isLoci(loci)) {
-        const groupCount = unit.links.edgeCount * 2
-        for (const b of loci.links) {
-            const unitIdx = group.unitIndexMap.get(b.aUnit.id)
-            if (unitIdx !== undefined) {
-                const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex)
-                if (idx !== -1) {
-                    if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
-                }
+    if (!Link.isLoci(loci)) return false
+    const { structure, group } = structureGroup
+    if (loci.structure !== structure) return false
+    const unit = group.units[0]
+    if (!Unit.isAtomic(unit)) return false
+    const groupCount = unit.links.edgeCount * 2
+    for (const b of loci.links) {
+        const unitIdx = group.unitIndexMap.get(b.aUnit.id)
+        if (unitIdx !== undefined) {
+            const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex)
+            if (idx !== -1) {
+                if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
             }
         }
     }

+ 20 - 18
src/mol-repr/structure/visual/util/element.ts

@@ -16,6 +16,7 @@ import { PickingId } from 'mol-geo/geometry/picking';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { VisualContext } from 'mol-repr';
 import { Theme } from 'mol-geo/geometry/geometry';
+import { StructureGroup } from 'mol-repr/structure/units-visual';
 
 export interface ElementSphereMeshProps {
     detail: number,
@@ -49,23 +50,23 @@ export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, st
     return meshBuilder.getMesh()
 }
 
-export function markElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
-    const elementCount = group.elements.length
-
+export function markElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
     let changed = false
-    if (StructureElement.isLoci(loci)) {
-        for (const e of loci.elements) {
-            const unitIdx = group.unitIndexMap.get(e.unit.id)
-            if (unitIdx !== undefined) {
-                if (Interval.is(e.indices)) {
-                    const start = unitIdx * elementCount + Interval.start(e.indices);
-                    const end = unitIdx * elementCount + Interval.end(e.indices);
-                    if (apply(Interval.ofBounds(start, end))) changed = true
-                } else {
-                    for (let i = 0, _i = e.indices.length; i < _i; i++) {
-                        const idx = unitIdx * elementCount + e.indices[i];
-                        if (apply(Interval.ofSingleton(idx))) changed = true
-                    }
+    if (!StructureElement.isLoci(loci)) return false
+    const { structure, group } = structureGroup
+    if (loci.structure !== structure) return false
+    const elementCount = group.elements.length
+    for (const e of loci.elements) {
+        const unitIdx = group.unitIndexMap.get(e.unit.id)
+        if (unitIdx !== undefined) {
+            if (Interval.is(e.indices)) {
+                const start = unitIdx * elementCount + Interval.start(e.indices);
+                const end = unitIdx * elementCount + Interval.end(e.indices);
+                if (apply(Interval.ofBounds(start, end))) changed = true
+            } else {
+                for (let i = 0, _i = e.indices.length; i < _i; i++) {
+                    const idx = unitIdx * elementCount + e.indices[i];
+                    if (apply(Interval.ofSingleton(idx))) changed = true
                 }
             }
         }
@@ -73,12 +74,13 @@ export function markElement(loci: Loci, group: Unit.SymmetryGroup, apply: (inter
     return changed
 }
 
-export function getElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+export function getElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
     if (id === objectId) {
+        const { structure, group } = structureGroup
         const unit = group.units[instanceId]
         const indices = OrderedSet.ofSingleton(groupId as StructureElement.UnitIndex);
-        return StructureElement.Loci([{ unit, indices }])
+        return StructureElement.Loci(structure, [{ unit, indices }])
     }
     return EmptyLoci
 }

+ 25 - 21
src/mol-repr/structure/visual/util/nucleotide.ts

@@ -10,6 +10,7 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
+import { StructureGroup } from 'mol-repr/structure/units-visual';
 
 export namespace NucleotideLocationIterator {
     export function fromGroup(group: Unit.SymmetryGroup): LocationIterator {
@@ -28,41 +29,44 @@ export namespace NucleotideLocationIterator {
     }
 }
 
-export function getNucleotideElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+export function getNucleotideElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
     if (id === objectId) {
+        const { structure, group } = structureGroup
         const unit = group.units[instanceId]
         if (Unit.isAtomic(unit)) {
             const unitIndex = OrderedSet.indexOf(unit.elements, unit.nucleotideElements[groupId]) as StructureElement.UnitIndex
             if (unitIndex !== -1) {
                 const indices = OrderedSet.ofSingleton(unitIndex)
-                return StructureElement.Loci([{ unit, indices }])
+                return StructureElement.Loci(structure, [{ unit, indices }])
             }
         }
     }
     return EmptyLoci
 }
 
-export function markNucleotideElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
+export function markNucleotideElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
     let changed = false
-    const u = group.units[0]
-    if (StructureElement.isLoci(loci) && Unit.isAtomic(u)) {
-        const groupCount = u.nucleotideElements.length
-        for (const e of loci.elements) {
-            const unitIdx = group.unitIndexMap.get(e.unit.id)
-            if (unitIdx !== undefined && Unit.isAtomic(e.unit)) {
-                if (Interval.is(e.indices)) {
-                    const min = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)])
-                    const max = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)])
-                    if (min !== -1 && max !== -1) {
-                        if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
-                    }
-                } else {
-                    for (let i = 0, _i = e.indices.length; i < _i; i++) {
-                        const idx = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[e.indices[i]])
-                        if (idx !== -1) {
-                            if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
-                        }
+    if (!StructureElement.isLoci(loci)) return false
+    const { structure, group } = structureGroup
+    if (loci.structure !== structure) return false
+    const unit = group.units[0]
+    if (!Unit.isAtomic(unit)) return false
+    const groupCount = unit.nucleotideElements.length
+    for (const e of loci.elements) {
+        const unitIdx = group.unitIndexMap.get(e.unit.id)
+        if (unitIdx !== undefined && Unit.isAtomic(e.unit)) {
+            if (Interval.is(e.indices)) {
+                const min = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)])
+                const max = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)])
+                if (min !== -1 && max !== -1) {
+                    if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
+                }
+            } else {
+                for (let i = 0, _i = e.indices.length; i < _i; i++) {
+                    const idx = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[e.indices[i]])
+                    if (idx !== -1) {
+                        if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
                     }
                 }
             }

+ 38 - 34
src/mol-repr/structure/visual/util/polymer.ts

@@ -10,6 +10,7 @@ import { OrderedSet, Interval } from 'mol-data/int';
 import { EmptyLoci, Loci } from 'mol-model/loci';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
+import { StructureGroup } from 'mol-repr/structure/units-visual';
 
 export * from './polymer/backbone-iterator'
 export * from './polymer/gap-iterator'
@@ -64,9 +65,10 @@ export namespace PolymerGapLocationIterator {
     }
 }
 
-export function getPolymerElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+export function getPolymerElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
     if (id === objectId) {
+        const { structure, group } = structureGroup
         const unit = group.units[instanceId]
         if (unit === undefined) {
             console.log(id, { objectId, instanceId, groupId }, group.units)
@@ -74,32 +76,32 @@ export function getPolymerElementLoci(pickingId: PickingId, group: Unit.Symmetry
         const unitIndex = OrderedSet.indexOf(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex
         if (unitIndex !== -1) {
             const indices = OrderedSet.ofSingleton(unitIndex)
-            return StructureElement.Loci([{ unit, indices }])
+            return StructureElement.Loci(structure, [{ unit, indices }])
         }
     }
     return EmptyLoci
 }
 
-export function markPolymerElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
-    const groupCount = group.units[0].polymerElements.length
-
+export function markPolymerElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
     let changed = false
-    if (StructureElement.isLoci(loci)) {
-        for (const e of loci.elements) {
-            const unitIdx = group.unitIndexMap.get(e.unit.id)
-            if (unitIdx !== undefined) {
-                if (Interval.is(e.indices)) {
-                    const min =  + OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.min(e.indices)])
-                    const max = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.max(e.indices)])
-                    if (min !== -1 && max !== -1) {
-                        if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
-                    }
-                } else {
-                    for (let i = 0, _i = e.indices.length; i < _i; i++) {
-                        const idx = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[e.indices[i]])
-                        if (idx !== -1) {
-                            if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
-                        }
+    if (!StructureElement.isLoci(loci)) return false
+    const { structure, group } = structureGroup
+    if (loci.structure !== structure) return false
+    const groupCount = group.units[0].polymerElements.length
+    for (const e of loci.elements) {
+        const unitIdx = group.unitIndexMap.get(e.unit.id)
+        if (unitIdx !== undefined) {
+            if (Interval.is(e.indices)) {
+                const min = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.min(e.indices)])
+                const max = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.max(e.indices)])
+                if (min !== -1 && max !== -1) {
+                    if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
+                }
+            } else {
+                for (let i = 0, _i = e.indices.length; i < _i; i++) {
+                    const idx = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[e.indices[i]])
+                    if (idx !== -1) {
+                        if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
                     }
                 }
             }
@@ -108,31 +110,33 @@ export function markPolymerElement(loci: Loci, group: Unit.SymmetryGroup, apply:
     return changed
 }
 
-export function getPolymerGapElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
     if (id === objectId) {
+        const { structure, group } = structureGroup
         const unit = group.units[instanceId]
         const unitIndexA = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId]) as StructureElement.UnitIndex
         const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex
         if (unitIndexA !== -1 && unitIndexB !== -1) {
-            return Link.Loci([ Link.Location(unit, unitIndexA, unit, unitIndexB) ])
+            return Link.Loci(structure, [ Link.Location(unit, unitIndexA, unit, unitIndexB) ])
         }
     }
     return EmptyLoci
 }
 
-export function markPolymerGapElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
+export function markPolymerGapElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
     let changed = false
-    if (Link.isLoci(loci)) {
-        const groupCount = group.units[0].gapElements.length
-        for (const b of loci.links) {
-            const unitIdx = group.unitIndexMap.get(b.aUnit.id)
-            if (unitIdx !== undefined) {
-                const idxA = OrderedSet.indexOf(b.aUnit.gapElements, b.aUnit.elements[b.aIndex])
-                const idxB = OrderedSet.indexOf(b.bUnit.gapElements, b.bUnit.elements[b.bIndex])
-                if (idxA !== -1 && idxB !== -1) {
-                    if (apply(Interval.ofSingleton(unitIdx * groupCount + idxA))) changed = true
-                }
+    if (!Link.isLoci(loci)) return false
+    const { structure, group } = structureGroup
+    if (loci.structure !== structure) return false
+    const groupCount = group.units[0].gapElements.length
+    for (const b of loci.links) {
+        const unitIdx = group.unitIndexMap.get(b.aUnit.id)
+        if (unitIdx !== undefined) {
+            const idxA = OrderedSet.indexOf(b.aUnit.gapElements, b.aUnit.elements[b.aIndex])
+            const idxB = OrderedSet.indexOf(b.bUnit.gapElements, b.bUnit.elements[b.bIndex])
+            if (idxA !== -1 && idxB !== -1) {
+                if (apply(Interval.ofSingleton(unitIdx * groupCount + idxA))) changed = true
             }
         }
     }