in packages/maker.js/src/core/chain.ts [137:291]
export function findChains(modelContext: IModel, ...args: any[]): IChain[] | IChainsMap {
var options: IFindChainsOptions;
var callback: IChainCallback;
switch (args.length) {
case 1:
if (typeof args[0] === 'function') {
callback = args[0];
} else {
options = args[0];
}
break;
case 2:
callback = args[0];
options = args[1];
break;
}
var opts: IFindChainsOptions = {
pointMatchingDistance: .005
};
extendObject(opts, options);
const pointGraphsByLayer: { [layer: string]: PointGraph<IChainLink>; } = {};
var chainsByLayer: IChainsMap = {};
var ignored: { [layer: string]: IWalkPath[]; } = {};
var walkOptions: IWalkOptions = {
onPath: function (walkedPath: IWalkPath) {
var layer = opts.byLayers ? walkedPath.layer : '';
if (!pointGraphsByLayer[layer]) {
pointGraphsByLayer[layer] = new PointGraph<IChainLink>();
}
const pointGraph = pointGraphsByLayer[layer];
var pathLength = measure.pathLength(walkedPath.pathContext);
//circles are loops by nature
if (
walkedPath.pathContext.type === pathType.Circle ||
(walkedPath.pathContext.type === pathType.Arc && round(angle.ofArcSpan(walkedPath.pathContext as IPathArc) - 360) === 0) ||
(walkedPath.pathContext.type === pathType.BezierSeed && measure.isPointEqual(walkedPath.pathContext.origin, (walkedPath.pathContext as IPathBezierSeed).end, opts.pointMatchingDistance))
) {
var chain: IChain = {
links: [{
walkedPath: walkedPath,
reversed: null,
endPoints: null,
pathLength: pathLength
}],
endless: true,
pathLength: pathLength
};
//store circles so that layers fire grouped
if (!chainsByLayer[layer]) {
chainsByLayer[layer] = [];
}
chainsByLayer[layer].push(chain);
} else {
//don't add lines which are 5x shorter than the tolerance
if (pathLength < opts.pointMatchingDistance / 5) {
if (!ignored[layer]) {
ignored[layer] = [];
}
ignored[layer].push(walkedPath);
return;
}
//gather both endpoints from all non-circle segments
const endPoints = point.fromPathEnds(walkedPath.pathContext, walkedPath.offset);
for (var i = 0; i < 2; i++) {
var link: IChainLink = {
walkedPath: walkedPath,
endPoints: endPoints,
reversed: i != 0,
pathLength: pathLength
};
let valueId = pointGraph.insertValue(link);
pointGraph.insertValueIdAtPoint(valueId, endPoints[i]);
}
}
}
};
if (opts.shallow) {
walkOptions.beforeChildWalk = function () { return false; };
}
var beziers: IWalkModel[];
if (opts.unifyBeziers) {
beziers = getBezierModels(modelContext);
swapBezierPathsWithSeeds(beziers, true);
}
walk(modelContext, walkOptions);
for (let layer in pointGraphsByLayer) {
let pointGraph = pointGraphsByLayer[layer];
pointGraph.mergeNearestSinglePoints(opts.pointMatchingDistance);
var loose: IWalkPath[] = [];
if (!chainsByLayer[layer]) {
chainsByLayer[layer] = [];
}
//follow paths to find endless chains
followLinks(
pointGraph,
function (chain: IChain, checkEndless: boolean) {
if (checkEndless) {
chain.endless = measure.isPointEqual(linkEndpoint(chain.links[0], true), linkEndpoint(chain.links[chain.links.length - 1], false), opts.pointMatchingDistance);
} else {
chain.endless = !!chain.endless;
}
chainsByLayer[layer].push(chain);
},
function (walkedPath: IWalkPath) {
loose.push(walkedPath);
}
);
//sort to return largest chains first
chainsByLayer[layer].sort((a: IChain, b: IChain) => { return b.pathLength - a.pathLength });
if (opts.contain) {
var containChainsOptions: IContainChainsOptions = isObject(opts.contain) ? opts.contain as IContainChainsOptions : { alternateDirection: false };
var containedChains = getContainment(chainsByLayer[layer], containChainsOptions);
chainsByLayer[layer] = containedChains;
}
if (callback) callback(chainsByLayer[layer], loose, layer, ignored[layer]);
}
if (beziers) {
swapBezierPathsWithSeeds(beziers, false);
}
if (opts.byLayers) {
return chainsByLayer;
} else {
return chainsByLayer[''];
}
}