Skip to main content
Participating Frequently
August 25, 2020
Answered

I need a script that groups filled paths by color for paintings by numbers.

  • August 25, 2020
  • 2 replies
  • 13071 views

Hello folks,

In a recent post (https://community.adobe.com/t5/illustrator/script-insert-text-number-in-the-middle-of-visible-bounds-of-the-each-object/td-p/8088914/page/5?page=1) I was reading on the following matter: the author uploaded a script that groups filled paths by color for paintings by numbers. However, when I tried to apply it to my Illustrator, it didn't work. Instead of showing different numbers in each field, I only get one and the same number.

 

I would appreciate it if someone could help me with a new, working script. I am also willing to pay for one.

 

Cheers.

Correct answer Charu Rajput

Hi,

Another version of the script. This is handling just pathItems and CMYK colors.  You can test it for simple sneario. This is not final and clean version. Code reference from the same link that you have shared

 

 

 

#target illustrator
    
var doc = app.activeDocument;
function main() {
    var _pageItems = doc.pageItems;
    var _layersCollection = [];
    for (var p = 0; p < _pageItems.length; p++) {
        collectItemsBasedOnColors(_pageItems[p], _layersCollection);
    }
    lays = doc.layers;
    WORK_LAY = lays.add();
    NUM_LAY = lays.add();

    for (var l = 0; l < _layersCollection.length; l++) {
        process(_layersCollection[l].pathItems, false, (l + 1));
    }
}

function process(items, isCompound, _num) {
    var j = 0,
        b, xy, s, p, op;

    for (; j < items.length; j++) {
        // process each pathItem
        op = items[j];
        // add stroke
        if (isCompound) {
            strokeComPath(op);
        } else {
            !op.closed && op.closed = true;
            op.filled = false;
            op.stroked = true;
        };
        b = getCenterBounds(op);
        xy = [b[0] + (b[2] - b[0]) / 2, b[1] + (b[3] - b[1]) / 2];
        s = (
            Math.min(op.height, op.width) < 20 ||
            (op.area && Math.abs(op.area) < 150)
        ) ? 4 : 6; // adjust font size for small area paths.
        add_nums(_num, xy, s);
    }
}

function collectItemsBasedOnColors(myCurrentObject, _layersCollection) {
    var myLayerName = "Unknown";
    if (myCurrentObject.typename == "PathItem") {
        if (myCurrentObject.filled == true) {
            if (myCurrentObject.fillColor == "[CMYKColor]") {
                myLayerName = "CMYK: " + Math.round(myCurrentObject.fillColor.cyan) + "," + Math.round(myCurrentObject.fillColor.magenta) + "," + Math.round(myCurrentObject.fillColor.yellow) + "," + Math.round(myCurrentObject.fillColor.black);
            }
        }
        try {
            var _layer = doc.layers.getByName(myLayerName);
            myCurrentObject.move(_layer, ElementPlacement.PLACEATBEGINNING);
        }

        catch (e) {
            var _layer = doc.layers.add()
            _layer.name = myLayerName
            myCurrentObject.move(_layer, ElementPlacement.PLACEATBEGINNING);
            _layersCollection.push(_layer);
        }
    }
}

function getCenterBounds(op) {
    var originalMinSize = getMinVisibleSize(op.visibleBounds);

    var p = applyOffset(op, false);

    if (getMinVisibleSize(p.visibleBounds) > originalMinSize) {
        // in some cases, path p becomes larger for some unknown reason
        p.remove();
        p = applyOffset(op, true);
    }

    var b = p.visibleBounds;

    if (getMinVisibleSize(b) > 10) {
        activeDocument.selection = [p];
        executeMenuCommand("expandStyle");
        p = activeDocument.selection[0];
        if (p.typename == "CompoundPathItem") {
            b = findBestBounds(op, p);
        }
    }

    p.remove();
    return b;
}

function getMinVisibleSize(b) {
    var s = Math.min(b[2] - b[0], b[1] - b[3]);
    return Math.abs(s);
}

function add_nums(n, xy, s) {
    var txt = NUM_LAY.textFrames.add();

    txt.contents = n;
    txt.textRange.justification = Justification.CENTER;
    txt.textRange.characterAttributes.size = s;
    txt.position = [xy[0] - txt.width / 2, xy[1] + txt.height / 2];
}


function applyOffset(op, checkBounds) {
    var p = op.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING),
        // offset value the small the better, but meantime more slow. 
        offset = function () {
            var minsize = Math.min(p.width, p.height);
            if (minsize >= 50) {
                return '-1'
            } else if (20 < minsize && minsize < 50) {
                return '-0.5'
            } else {
                return '-0.2' // 0.2 * 2 (both side ) * 50 (Times) = 20
            }
        },
        xmlstring = '<LiveEffect name="Adobe Offset Path"><Dict data="I jntp 2 R mlim 4 R ofst #offset"/></LiveEffect>'
            .replace('#offset', offset()),
        TIMES = 100; // if shapes are too large, should increase the value.

    if (checkBounds) {
        // check its size only if it needs, because it's too slow
        while (TIMES-- && getMinVisibleSize(p.visibleBounds) > 3) p.applyEffect(xmlstring);
    } else {
        while (TIMES--) p.applyEffect(xmlstring);
    }
    return p;
}

function getGeometricCenter(p) {
    var b = p.geometricBounds;
    return [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2];
}

// returns square of distance between p1 and p2
function getDist2(p1, p2) {
    return Math.pow(p1[0] + p2[0], 2) + Math.pow(p1[1] + p2[1], 2);
}

// returns visibleBounds of a path in a compoundPath p
// which is closest to center of the original path op
function findBestBounds(op, p) {
    var opc = getGeometricCenter(op);
    var idx = 0,
        d;
    var minD = getDist2(opc, getGeometricCenter(p.pathItems[0]));
    for (var i = 0, iEnd = p.pathItems.length; i < iEnd; i++) {
        d = getDist2(opc, getGeometricCenter(p.pathItems[i]));
        if (d < minD) {
            minD = d;
            idx = i;
        }
    }
    return p.pathItems[idx].visibleBounds;
}

main();

 

 

 

Running with attached sample file. Ofcourse more can be done to handle other scenarios.

Sample File 

2 replies

Inventsable
Legend
February 6, 2023
Participant
February 6, 2023

It's not working with compound paths 😞

Inventsable
Legend
April 30, 2023

It was never intended to. Compound Paths can have multiple fills within their children paths, and you wouldn't be able to group those children paths with other paths without breaking the compound.

Charu Rajput
Community Expert
Charu RajputCommunity ExpertCorrect answer
Community Expert
August 25, 2020

Hi,

Another version of the script. This is handling just pathItems and CMYK colors.  You can test it for simple sneario. This is not final and clean version. Code reference from the same link that you have shared

 

 

 

#target illustrator
    
var doc = app.activeDocument;
function main() {
    var _pageItems = doc.pageItems;
    var _layersCollection = [];
    for (var p = 0; p < _pageItems.length; p++) {
        collectItemsBasedOnColors(_pageItems[p], _layersCollection);
    }
    lays = doc.layers;
    WORK_LAY = lays.add();
    NUM_LAY = lays.add();

    for (var l = 0; l < _layersCollection.length; l++) {
        process(_layersCollection[l].pathItems, false, (l + 1));
    }
}

function process(items, isCompound, _num) {
    var j = 0,
        b, xy, s, p, op;

    for (; j < items.length; j++) {
        // process each pathItem
        op = items[j];
        // add stroke
        if (isCompound) {
            strokeComPath(op);
        } else {
            !op.closed && op.closed = true;
            op.filled = false;
            op.stroked = true;
        };
        b = getCenterBounds(op);
        xy = [b[0] + (b[2] - b[0]) / 2, b[1] + (b[3] - b[1]) / 2];
        s = (
            Math.min(op.height, op.width) < 20 ||
            (op.area && Math.abs(op.area) < 150)
        ) ? 4 : 6; // adjust font size for small area paths.
        add_nums(_num, xy, s);
    }
}

function collectItemsBasedOnColors(myCurrentObject, _layersCollection) {
    var myLayerName = "Unknown";
    if (myCurrentObject.typename == "PathItem") {
        if (myCurrentObject.filled == true) {
            if (myCurrentObject.fillColor == "[CMYKColor]") {
                myLayerName = "CMYK: " + Math.round(myCurrentObject.fillColor.cyan) + "," + Math.round(myCurrentObject.fillColor.magenta) + "," + Math.round(myCurrentObject.fillColor.yellow) + "," + Math.round(myCurrentObject.fillColor.black);
            }
        }
        try {
            var _layer = doc.layers.getByName(myLayerName);
            myCurrentObject.move(_layer, ElementPlacement.PLACEATBEGINNING);
        }

        catch (e) {
            var _layer = doc.layers.add()
            _layer.name = myLayerName
            myCurrentObject.move(_layer, ElementPlacement.PLACEATBEGINNING);
            _layersCollection.push(_layer);
        }
    }
}

function getCenterBounds(op) {
    var originalMinSize = getMinVisibleSize(op.visibleBounds);

    var p = applyOffset(op, false);

    if (getMinVisibleSize(p.visibleBounds) > originalMinSize) {
        // in some cases, path p becomes larger for some unknown reason
        p.remove();
        p = applyOffset(op, true);
    }

    var b = p.visibleBounds;

    if (getMinVisibleSize(b) > 10) {
        activeDocument.selection = [p];
        executeMenuCommand("expandStyle");
        p = activeDocument.selection[0];
        if (p.typename == "CompoundPathItem") {
            b = findBestBounds(op, p);
        }
    }

    p.remove();
    return b;
}

function getMinVisibleSize(b) {
    var s = Math.min(b[2] - b[0], b[1] - b[3]);
    return Math.abs(s);
}

function add_nums(n, xy, s) {
    var txt = NUM_LAY.textFrames.add();

    txt.contents = n;
    txt.textRange.justification = Justification.CENTER;
    txt.textRange.characterAttributes.size = s;
    txt.position = [xy[0] - txt.width / 2, xy[1] + txt.height / 2];
}


function applyOffset(op, checkBounds) {
    var p = op.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING),
        // offset value the small the better, but meantime more slow. 
        offset = function () {
            var minsize = Math.min(p.width, p.height);
            if (minsize >= 50) {
                return '-1'
            } else if (20 < minsize && minsize < 50) {
                return '-0.5'
            } else {
                return '-0.2' // 0.2 * 2 (both side ) * 50 (Times) = 20
            }
        },
        xmlstring = '<LiveEffect name="Adobe Offset Path"><Dict data="I jntp 2 R mlim 4 R ofst #offset"/></LiveEffect>'
            .replace('#offset', offset()),
        TIMES = 100; // if shapes are too large, should increase the value.

    if (checkBounds) {
        // check its size only if it needs, because it's too slow
        while (TIMES-- && getMinVisibleSize(p.visibleBounds) > 3) p.applyEffect(xmlstring);
    } else {
        while (TIMES--) p.applyEffect(xmlstring);
    }
    return p;
}

function getGeometricCenter(p) {
    var b = p.geometricBounds;
    return [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2];
}

// returns square of distance between p1 and p2
function getDist2(p1, p2) {
    return Math.pow(p1[0] + p2[0], 2) + Math.pow(p1[1] + p2[1], 2);
}

// returns visibleBounds of a path in a compoundPath p
// which is closest to center of the original path op
function findBestBounds(op, p) {
    var opc = getGeometricCenter(op);
    var idx = 0,
        d;
    var minD = getDist2(opc, getGeometricCenter(p.pathItems[0]));
    for (var i = 0, iEnd = p.pathItems.length; i < iEnd; i++) {
        d = getDist2(opc, getGeometricCenter(p.pathItems[i]));
        if (d < minD) {
            minD = d;
            idx = i;
        }
    }
    return p.pathItems[idx].visibleBounds;
}

main();

 

 

 

Running with attached sample file. Ofcourse more can be done to handle other scenarios.

Sample File 

Best regards
Participating Frequently
August 25, 2020

Charu Rajput
Community Expert
Community Expert
August 25, 2020

Just updated the code, keep following line

var doc = app.activeDocument;

out the main() function.

Best regards