Skip to content

Commit adce07e

Browse files
committed
Anchor: add !anchor unchain and Anchor.unchainAnchorObjs API
1 parent 8161b2d commit adce07e

2 files changed

Lines changed: 116 additions & 4 deletions

File tree

Anchor/2.1.0/anchor.js

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ var Anchor = Anchor || (() => {
186186
...Object.keys(ALIAS_MAP),
187187
'remove', 'lock', 'unlock', 'center', 'update', 'info',
188188
'track', 'untrack', 'retrack',
189-
'chain',
189+
'chain', 'unchain',
190190
'ignore-selected', 'persist',
191191
'config',
192192
'--help',
@@ -1136,6 +1136,9 @@ var Anchor = Anchor || (() => {
11361136
`<b>${CMD_TOKEN} chain [component flags] [ignore-selected] [child_id...]</b>`,
11371137
'Mutually anchor tokens in a ring (A\u2192B, B\u2192C, C\u2192A). Move any one, all follow.',
11381138
'',
1139+
`<b>${CMD_TOKEN} unchain [ignore-selected] [child_id...]</b>`,
1140+
'Dissolve a chain ring. Select any one token in the ring.',
1141+
'',
11391142
`<b>${CMD_TOKEN} update [ignore-selected] [child_id...]</b>`,
11401143
'Force immediate transform sync.',
11411144
'',
@@ -1511,7 +1514,7 @@ var Anchor = Anchor || (() => {
15111514
// Only skip the first otherArg as a potential anchor ID when we're
15121515
// establishing a new anchor relationship AND it's actually a valid graphic.
15131516
// If there's no valid graphic as the first arg, all otherArgs are child IDs.
1514-
const ACTION_FLAGS = ['remove', 'lock', 'unlock', 'center', 'update', 'info', 'track', 'untrack', 'retrack', 'chain'];
1517+
const ACTION_FLAGS = ['remove', 'lock', 'unlock', 'center', 'update', 'info', 'track', 'untrack', 'retrack', 'chain', 'unchain'];
15151518
const hasAction = ACTION_FLAGS.some(f => flags.has(f));
15161519
const isNewAnchor = !hasAction && (Object.keys(FLAG_EXPANSIONS).some(f => flags.has(f)) || flags.size === 0);
15171520
const firstArgIsAnchor = isNewAnchor &&
@@ -1656,6 +1659,21 @@ var Anchor = Anchor || (() => {
16561659
}
16571660
}
16581661

1662+
// Unchain — dissolve a chain ring from any member
1663+
if (flags.has('unchain')) {
1664+
const ids = resolveChildIds(msg, flags, otherArgs);
1665+
if (ids.length === 0) {
1666+
reply(msg, 'Error', 'Select or specify a token in the chain.');
1667+
} else {
1668+
var unchained = unchainAnchorObjs(ids[0]);
1669+
if (unchained) {
1670+
reply(msg, 'Info', 'Unchained ' + unchained.length + ' tokens.');
1671+
} else {
1672+
reply(msg, 'Error', 'Token is not part of a chain ring.');
1673+
}
1674+
}
1675+
}
1676+
16591677
// Info
16601678
if (flags.has('info')) {
16611679
if (childIds.length > 0) {
@@ -1745,6 +1763,43 @@ var Anchor = Anchor || (() => {
17451763
}
17461764
};
17471765

1766+
/**
1767+
* Walk the anchor chain from a starting token and find the ring.
1768+
* Returns the array of IDs forming the ring, or null if no ring found.
1769+
* The starting token does not need to be in the ring itself — if it's
1770+
* a child of a ring member, the ring is still found.
1771+
*/
1772+
const walkChain = (startId) => {
1773+
const s = state[SCRIPT_NAME];
1774+
const visited = [];
1775+
var current = startId;
1776+
while (true) {
1777+
var info = s.anchorInfoByChildId[current];
1778+
if (!info) return null; // not a child — dead end, no ring
1779+
visited.push(current);
1780+
var nextId = info.anchor_id;
1781+
var idx = visited.indexOf(nextId);
1782+
if (idx !== -1) return visited.slice(idx); // found the ring
1783+
current = nextId;
1784+
if (visited.length > 1000) return null; // safety cap
1785+
}
1786+
};
1787+
1788+
/**
1789+
* Unchain a ring of anchored tokens. Given any token ID in the ring,
1790+
* walks the chain and removes all anchor relationships.
1791+
* Returns the array of unchained IDs, or null if the token is not in a ring.
1792+
*/
1793+
const unchainAnchorObjs = (startId) => {
1794+
var ids = walkChain(startId);
1795+
if (!ids) {
1796+
log(SCRIPT_NAME + ': unchainAnchorObjs — token is not part of a chain ring.');
1797+
return null;
1798+
}
1799+
ids.forEach(function(id) { removeAnchor(id); });
1800+
return ids;
1801+
};
1802+
17481803
/**
17491804
* Programmatically create an invisible auto-anchor token for `obj` and
17501805
* establish the anchor relationship immediately.
@@ -2450,6 +2505,7 @@ var Anchor = Anchor || (() => {
24502505
lock,
24512506
unlock,
24522507
chainAnchorObjs,
2508+
unchainAnchorObjs,
24532509
},
24542510
};
24552511
})();

Anchor/anchor.js

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ var Anchor = Anchor || (() => {
186186
...Object.keys(ALIAS_MAP),
187187
'remove', 'lock', 'unlock', 'center', 'update', 'info',
188188
'track', 'untrack', 'retrack',
189-
'chain',
189+
'chain', 'unchain',
190190
'ignore-selected', 'persist',
191191
'config',
192192
'--help',
@@ -1136,6 +1136,9 @@ var Anchor = Anchor || (() => {
11361136
`<b>${CMD_TOKEN} chain [component flags] [ignore-selected] [child_id...]</b>`,
11371137
'Mutually anchor tokens in a ring (A\u2192B, B\u2192C, C\u2192A). Move any one, all follow.',
11381138
'',
1139+
`<b>${CMD_TOKEN} unchain [ignore-selected] [child_id...]</b>`,
1140+
'Dissolve a chain ring. Select any one token in the ring.',
1141+
'',
11391142
`<b>${CMD_TOKEN} update [ignore-selected] [child_id...]</b>`,
11401143
'Force immediate transform sync.',
11411144
'',
@@ -1511,7 +1514,7 @@ var Anchor = Anchor || (() => {
15111514
// Only skip the first otherArg as a potential anchor ID when we're
15121515
// establishing a new anchor relationship AND it's actually a valid graphic.
15131516
// If there's no valid graphic as the first arg, all otherArgs are child IDs.
1514-
const ACTION_FLAGS = ['remove', 'lock', 'unlock', 'center', 'update', 'info', 'track', 'untrack', 'retrack', 'chain'];
1517+
const ACTION_FLAGS = ['remove', 'lock', 'unlock', 'center', 'update', 'info', 'track', 'untrack', 'retrack', 'chain', 'unchain'];
15151518
const hasAction = ACTION_FLAGS.some(f => flags.has(f));
15161519
const isNewAnchor = !hasAction && (Object.keys(FLAG_EXPANSIONS).some(f => flags.has(f)) || flags.size === 0);
15171520
const firstArgIsAnchor = isNewAnchor &&
@@ -1656,6 +1659,21 @@ var Anchor = Anchor || (() => {
16561659
}
16571660
}
16581661

1662+
// Unchain — dissolve a chain ring from any member
1663+
if (flags.has('unchain')) {
1664+
const ids = resolveChildIds(msg, flags, otherArgs);
1665+
if (ids.length === 0) {
1666+
reply(msg, 'Error', 'Select or specify a token in the chain.');
1667+
} else {
1668+
var unchained = unchainAnchorObjs(ids[0]);
1669+
if (unchained) {
1670+
reply(msg, 'Info', 'Unchained ' + unchained.length + ' tokens.');
1671+
} else {
1672+
reply(msg, 'Error', 'Token is not part of a chain ring.');
1673+
}
1674+
}
1675+
}
1676+
16591677
// Info
16601678
if (flags.has('info')) {
16611679
if (childIds.length > 0) {
@@ -1745,6 +1763,43 @@ var Anchor = Anchor || (() => {
17451763
}
17461764
};
17471765

1766+
/**
1767+
* Walk the anchor chain from a starting token and find the ring.
1768+
* Returns the array of IDs forming the ring, or null if no ring found.
1769+
* The starting token does not need to be in the ring itself — if it's
1770+
* a child of a ring member, the ring is still found.
1771+
*/
1772+
const walkChain = (startId) => {
1773+
const s = state[SCRIPT_NAME];
1774+
const visited = [];
1775+
var current = startId;
1776+
while (true) {
1777+
var info = s.anchorInfoByChildId[current];
1778+
if (!info) return null; // not a child — dead end, no ring
1779+
visited.push(current);
1780+
var nextId = info.anchor_id;
1781+
var idx = visited.indexOf(nextId);
1782+
if (idx !== -1) return visited.slice(idx); // found the ring
1783+
current = nextId;
1784+
if (visited.length > 1000) return null; // safety cap
1785+
}
1786+
};
1787+
1788+
/**
1789+
* Unchain a ring of anchored tokens. Given any token ID in the ring,
1790+
* walks the chain and removes all anchor relationships.
1791+
* Returns the array of unchained IDs, or null if the token is not in a ring.
1792+
*/
1793+
const unchainAnchorObjs = (startId) => {
1794+
var ids = walkChain(startId);
1795+
if (!ids) {
1796+
log(SCRIPT_NAME + ': unchainAnchorObjs — token is not part of a chain ring.');
1797+
return null;
1798+
}
1799+
ids.forEach(function(id) { removeAnchor(id); });
1800+
return ids;
1801+
};
1802+
17481803
/**
17491804
* Programmatically create an invisible auto-anchor token for `obj` and
17501805
* establish the anchor relationship immediately.
@@ -2450,6 +2505,7 @@ var Anchor = Anchor || (() => {
24502505
lock,
24512506
unlock,
24522507
chainAnchorObjs,
2508+
unchainAnchorObjs,
24532509
},
24542510
};
24552511
})();

0 commit comments

Comments
 (0)