Copy link to clipboard
Copied
Hello! Adobe Illustrator CS 6
I have script, wich groups filled paths by colors. Link to script and traced image. Traced_image.rar - Google Drive
I have traced image, it has 25 colors. The script creates 25 groups, paths of each color in each group.
I need to modify script, i want to insert text number in visible middle of the each filled path. So one group is one number. From 1 to 25.
Can anyone help me?
Hello all, here comes the final version.
This time "offset path effect" been used. I thought this is a possible way at beginning, but never give it a try, until today.
It deal with both Path and Compound Path. Test the sample document, it only took 31 seconds.
Copy link to clipboard
Copied
Thanks Larry, I'll have a go with lower miters and see if it helps,
I report back.
Copy link to clipboard
Copied
Dear Qwertyfly, if u have any little peace of time, please take a look on my problem with this script. I hope u can help and finish this script. Thank you!
Copy link to clipboard
Copied
have not found any real help in lowering miters.
though it does change the results a bit.
the issue is with illustrator and its ability to expand outlines,
I'm a little stumped as to how we can get around this.
more thought needs to be put in to this...
here is a quick example of the issue.
I also played with rounded corners, this also helps a bit, but still no luck.
Copy link to clipboard
Copied
Ok, kinda got it, kinda.
I've resorted to incrementing the stroke width by 1 each iteration
this makes things a bit simpler but slower
using rounded corners had a better impact then a low miter.
so now just the issue of the artifacts illustrator gives us for free.
"how about we delete them"
this is where things get a little errr. lets call it sticky tape holding on my car bumper...
so I divide the area of the original object by 80 and compare that to each pathItem in the compound path.
deleting any that are smaller then the area/80.
on some shapes this can end up in a eternal loop, so once we have tried 100 iterations on a shape with no luck we change the divider to 50 and try again.
this variable toggles between 80 and 50 every time a shape takes more then that 100 steps.
I am sure this will break on some files.
but it does work on the A020- Trace.ai file you provided.
(as long as you run your script first, and make sure to select the 3rd radio option)
run time was 19min to complete on my machine.
sorry about all the added crap I have left in the script. all the console logs are probably slowing it down some.
we can clear them out when we are happy its running well.
// ADD NUMBERS TO FAT PART OF SHAPE
// Version 0.2
// IMPORTANT
// This is still in a beta phase, good chance it will not work as expected,
// Make sure all work is saved and you have coppies of any art you are working on
// author: QwertyFly
// contact: tristan@qwertyfly.com
function add_nums(){
// fiddle with these values to adjust speed vs accuracy
var percentage = 1; // take average betwenn hight and width and multiplies it by this to get starting stroke weight - 1 = 100%
var increment = 1; // amount to add to percentage on each iteration
var uselessSection = 80; // this is used to determin if a shape in the compound path is an illustrator anomally, not as reliable as I would like.
var doc = app.activeDocument;
var lays = doc.layers;
var layNames = [];
for(var i=0; i<lays.length; i++){
layNames.push(lays.name);
}
// new layers
var WORK_LAY = doc.layers.add();
WORK_LAY.name = "Working Layer";
var NUM_LAY = doc.layers.add();
NUM_LAY.name = "Numbers";
var currentItem = 0;
$.writeln("Total paths in doc = " + doc.pathItems.length);
// main working loop
for(var i=0; i<layNames.length; i++){
//process each layer
var lay = lays.getByName(layNames);
lay.name = lay.name + " Num:" + (i+1);
for(var j=0; j<lay.pathItems.length; j++){
currentItem++
$.writeln("---------------------------");
$.writeln(currentItem);
// process each pathItem
var pth = lay.pathItems
; var per = percentage; // reset the path percentage for each shape
var cent = [];
var Count = 0;
if(FatPart(pth) === "error"){
alert("Loop Error, try resetting\npercentage to 1,\nand\nincroment to .8\nin the script file");
return;
}
// Create the text frame
var txt = NUM_LAY.textFrames.add();
txt.contents = i+1;
txt.textRange.justification = Justification.CENTER;
txt.position = [cent[0]-txt.width/2,cent[1]+txt.height/2];
pth.filled = false;
pth.stroked = true;
}
}
//clean up
WORK_LAY.remove();
// find fat section of a shape
function FatPart(pth){
var success = false;
while(!success){
success = process();
// if recursive function runs too many times give option to exit
Count++;
if(Count%100 === 0){
$.writeln(Count);
if(uselessSection === 80){
uselessSection = 50;
}else{
uselessSection = 80;
}
//var keepGoing = confirm("Process function has run " + Count + " times on current object.\nWould you like to continue trying?");
var keepGoing = true;
if(!keepGoing){
return "error";
}
}
}
function process(){
// make sure we start with nothing selected
doc.selection = null;
// make a copy of the shape to work on
var item = pth.duplicate(WORK_LAY,ElementPlacement.PLACEATBEGINNING);
item.filled = false;
item.stroked = true;
item.strokeMiterLimit = 1;
item.strokeJoin = StrokeJoin.ROUNDENDJOIN;
// scale works out how thick the stroke shold be
var scale = per * increment;
item.strokeWidth = scale;
// select the items and outline the stroke
$.writeln("---" + scale);
item.selected = true;
app.executeMenuCommand ('OffsetPath v22');// = Outline Stroke
var sel = doc.selection;
if(sel[0].typename === "CompoundPathItem"){
if(sel[0].pathItems.length>2){
for( var k = sel[0].pathItems.length-1; k>-1; k--){
if(Math.abs(sel[0].pathItems
.area)<Math.abs(item.area)/uselessSection){ $.writeln("item Area/"+ uselessSection +" = " + item.area/uselessSection);
$.writeln("hole Area = " + sel[0].pathItems
.area); sel[0].pathItems
.remove(); }
}
}
if(sel[0].pathItems.length>2){
// too many paths, increase stroke and retry
sel[0].remove();
per = per + increment;
return false;
}else{
if(sel[0].pathItems.length === 2){
// Remove the larger of the 2 shapes in the compoundPath
if(Math.abs(sel[0].pathItems[0].area)>Math.abs(sel[0].pathItems[1].area)){
sel[0].pathItems[0].remove();
}else{
sel[0].pathItems[1].remove();
}
}else{
// shape filled completely, reduced stroke and retry
sel[0].remove()
per = per - increment/10;
return false;
}
// compare size against original - Aiming for under 10%
if(Math.abs(sel[0].pathItems[0].area)>Math.abs(item.area)/10){ // the 10 is ten percent of original size
sel[0].pathItems[0].remove();
per = per + increment;
return false;
}else{
// now we should have correct size to center text to
// return X,Y of shapes Center
// alert("found");
var B = sel[0].geometricBounds;
cent.push(B[0]+(B[2]-B[0])/2);
cent.push(B[1]+(B[3]-B[1])/2);
sel[0].remove();
return true;
}
}
}else{
// shape filled completely, reduced stroke and retry
sel[0].remove()
per = per - increment/10;
return false;
}
}
}
}
var timer = Date.now();
add_nums();
var x = (Date.now()-timer);
alert('conversion completed in:\n'
+ Math.floor(x/1000/60) + ' Minutes, '
+ Math.floor(x/1000%60) + ' Seconds, and '
+ x%1000 + ' Milliseconds');
Copy link to clipboard
Copied
I should add that it looks like your A020 - Trace.ai file has some shapes doubled up.
this is due to the 3rd radio button of your script.
the 3rd option breaks compound paths.
Ideally we should use the second option.
My script, if I guess correctly, will have many complaints about having to deal with compound paths and groups.
I'll think on the best solution to this.
Copy link to clipboard
Copied
Dear Qwertyfly, please tell me, what version of illustrator u have started by this script? My CC version just frozen every time for many hours....
Copy link to clipboard
Copied
I'm running CC 19.2.1
I doubt it is due to the version, any CC should be fine.
It will have more to do with dodgy code.
do you first run your script the option 3 checked?
I ask because the code you have posed has a few typos.
"
near the end: release_to_layers_dialog.close(); should be release_to_layers_window.close();
also I had to change the height of the section of the UI with the radio buttons so I could see option 3
"
If you open the script in ESTK and run it from there you will see the Javascript Console start Logging a whole lot of crap.
Look for a stuck loop.
I'll try to find a few mins to go through and fix the logs to be more informative.
there will be a shape that it cant deal with.
you could try a different number instead of 80 or 50
I'll post back once I have it updated
Copy link to clipboard
Copied
Hi Qwerty, follow your point I get another way, the result is not so good, but anyway here it is.
var doc = activeDocument,
oPath = app.selection[0],
per = 0.48, // can be adjusted
radius = Math.min(oPath.width, oPath.height) * per,
times = 0,
maxTimes = 5 , // may be more times is better
i = 0,
rasterOpts = new RasterizeOptions,
xmlstring, oTrace, options, countOld;
// apply feather effect
xmlstring = '<LiveEffect name="Adobe Fuzzy Mask"><Dict data="R Radius #rad"/></LiveEffect>'
.replace('#rad', '' + radius);
oPath.applyEffect(xmlstring);
// rasterize and trace
rasterOpts.resolution = 72;
oTrace = doc.rasterize(oPath, oPath.geometricBounds, rasterOpts).trace();
options = oTrace.tracing.tracingOptions;
options.tracingMode = TracingModeType.TRACINGMODEBLACKANDWHITE;
options.tracingColorTypeValue = TracingColorType.TRACINGFULLCOLOR;
options.threshold = 255;
options.grayLevels = 50;
options.pathFidelity = 90;
options.cornerFidelity = 80;
options.noiseFidelity = 20;
options.tracingMethod = TracingMethodType.TRACINGMETHODABUTTING;
options.fills = true;
options.maxStrokeWeight = 10;
options.snapCurveToLines = true;
options.ignoreWhite = true;
app.redraw();
// loop until the result path small and clean enough
for (var i = 0; i < 50; i++) {
countOld = oTrace.tracing.anchorCount;
if (oTrace.tracing.anchorCount === 4) {
oTrace.tracing.tracingOptions.threshold--;
app.redraw();
if (check(maxTimes)) {
break
}
} else if (oTrace.tracing.anchorCount > 10) {
oTrace.tracing.tracingOptions.threshold--;
app.redraw();
//$.writeln (oTrace.tracing.anchorCount);
if (check(maxTimes)) {
break
}
$.writeln(oTrace.tracing.anchorCount);
} else if (oTrace.tracing.pathCount > 1) {
oTrace.tracing.tracingOptions.threshold--;
if (check(maxTimes)) {
break
}
app.redraw();
} else {
break;
}
}
function check(maxTimes) {
if (oTrace.tracing.anchorCount === countOld) {
times++;
return times > maxTimes ? true : false;
}
}
Select one path before fun, and the result as picture.
Copy link to clipboard
Copied
As I think "How about aligh stroke to inside?", i finally get this. Before run the script, you need create a graphic style named "stroke inside", which set the path: none fill, 1pt stroke width, and make sure aligh stroke to inside.
function add_nums() {
// fiddle with these values to adjust speed vs accuracy
var percentage = 1; // take average betwenn hight and width and multiplies it by this to get starting stroke weight - 1 = 100%
var increment = .4; // amount to add to percentage on each iteration
var failure = 0; // log failured path
var doc = app.activeDocument;
var lays = doc.layers;
var layNames = [];
for (var i = 0; i < lays.length; i++) {
layNames.push(lays.name);
}
// new layers
var WORK_LAY = doc.layers.add();
WORK_LAY.name = "Working Layer";
var NUM_LAY = doc.layers.add();
NUM_LAY.name = "Numbers";
// main working loop
for (var i = 0; i < layNames.length; i++) {
//process each layer
var lay = lays.getByName(layNames);
lay.name = lay.name + " Num:" + (i + 1);
for (var j = 0; j < lay.pathItems.length; j++) {
// process each pathItem
var pth = lay.pathItems
; var per = percentage; // reset the path percentage for each shape
var cent = [];
var Count = 0;
if (FatPart(pth) !== 'error') {
pth.filled = false;
pth.stroked = true;
}
// Create the text frame
var txt = NUM_LAY.textFrames.add();
txt.contents = i + 1;
txt.textRange.justification = Justification.CENTER;
txt.position = [cent[0] - txt.width / 2, cent[1] + txt.height / 2];
}
}
//clean up
WORK_LAY.remove();
$.writeln(failure);
// find fat section of a shape
function FatPart(pth) {
var success = false;
while (!success) {
success = process();
// if recursive function runs too many times give option to exit
Count++;
if (Count % 40 === 0) {
//$.writeln("Process function has run " + Count + " times on current object.\nWould you like to continue trying?");
failure++;
return 'error';
}
}
function process() {
// make sure we start with nothing selected
doc.selection = null;
// make a copy of the shape to work on
var item = pth.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING);
// make sure the path is closed as we want the aligh stroke to inside
item.closed = true;
// apply the "aligh stroke to inside" graphicStyle by name
doc.graphicStyles.getByName('stroke inside').applyTo(item);
// scale works out how thick the stroke shold be
var scale = Math.min(item.height, item.width) / 10 * per;
item.strokeWidth = scale;
//$.writeln("scale:" + scale);
// select the items and outline the stroke
item.selected = true;
app.executeMenuCommand('OffsetPath v22'); // = Outline Stroke
var sel = doc.selection;
if (sel[0].typename === "CompoundPathItem") {
if (sel[0].pathItems.length > 2) {
// too many paths, increase stroke and retry
sel[0].remove()
per = per + increment;
return false;
} else {
// Remove the larger of the 2 shapes in the compoundPath
if (Math.abs(sel[0].pathItems[0].area) > Math.abs(sel[0].pathItems[1].area)) {
sel[0].pathItems[0].remove();
} else {
sel[0].pathItems[1].remove();
}
// compare size against original - Aiming for under 10%
if (Math.abs(sel[0].pathItems[0].area) > Math.abs(item.area) / 10) { // the 10 is ten percent of original size
sel[0].pathItems[0].remove();
per = per + increment;
return false;
} else {
// now we should have correct size to center text to
// return X,Y of shapes Center
var B = sel[0].geometricBounds;
cent.push(B[0] + (B[2] - B[0]) / 2);
cent.push(B[1] + (B[3] - B[1]) / 2);
sel[0].remove()
//$.writeln('ok')
return true;
}
}
} else {
// shape filled completely, reduced stroke and retry
sel[0].remove()
per = per - 0.02;
return false;
}
}
}
}
var timer = Date.now(); // 7'20'' with 7 failures
add_nums();
var x = (Date.now() - timer);
alert('conversion completed in:\n' +
Math.floor(x / 1000 / 60) + ' Minutes, ' +
Math.floor(x / 1000 % 60) + ' Seconds, and ' +
x % 1000 + ' Milliseconds');
Result: tested the same document as you, time used was 7'20'' ,with 7 path failures.
And also I have yet another thought: since the stroke is inside, we can compare area directly. Will give it a try.
Copy link to clipboard
Copied
Dear moluapple! It really works, and very fast! Stroke inside sometimes makes strange things, for example some objects can appear into another object, i am trying now to toggle with settings.
Copy link to clipboard
Copied
And sometimes number going near object, but not inside. I don't know why)
Copy link to clipboard
Copied
wow. to have my script tweaked by moluapple.
I would like to say that you are part of a very small group of people that I have carved stone statues of, and displayed in an A shaped alter.
more seriously I would like to that you for the basics of creating actions via javascript.
I have used this many times to overcome some of the shortfalls in illy scripting.
I don't have the time now but will have a look over your code when I do.
I remember trying align to inside but I did not use a graphic style, so it may have been why it did not work.
I think this idea is a great one, I can think of many applications for it.
I think I need to really think about a pure math solution, as this could be a great tool if it was instant and reliable.
be sure i'll post here any headway i make.
Copy link to clipboard
Copied
this thread is getting better, although I agree a Mathematical solution would be best, it fascinates me how ingenious solutions are found with the limited resources we currently have.
great work guys
Copy link to clipboard
Copied
Thank you very much! Swap figures manually course better than creating entirely by hand. But I'll be happy, if you can fully automate the placement of numbers to the gravitational center. I really hope for your skills!
Copy link to clipboard
Copied
I was thinking you can put a circle at every pathpoint of a path, then have your alignment set to "selection" and use some menu command to do alignment on a selection of those circles to have the program automatically center up the many little items to find a 'center', however 2 problems is that I can't find a menu command- so it would have to be action-executed, and also you can have one big zone made by very few pathpoints and a little zone made with very many pathpoints, so the bias goes toward the point-concentration in manual testing.
Copy link to clipboard
Copied
Ok, finally success to deal with Compound Path, using "stroke inside & compare area" method. Qwertyfly...
Select one Compound Path item and run the code, will draw a circle in the "fat center". Make sure the "stroke inside" graphic style exists.
var doc = app.activeDocument,
pth = app.selection[0],
Count = 0,
per = 1, // take average betwenn hight and width and multiplies it by this to get starting stroke weight - 1 = 100%
increment = .2, // base amount to add to percentage on each iteration
TIMES = 20, // when error, try more higher value,
AREA_PER = 0.02, // 2% of origin path's area, the lower the better, but more slow
workLay,
areaArr = logAreas(pth);
try {
workLay = doc.layers.getByName('workLay');
} catch (e) {
workLay = doc.layers.add();
workLay.name = 'workLay';
}
FatPart(pth);
function FatPart(pth) {
var success = false;
while (!success) {
success = process();
// if recursive function runs too many times give option to exit
Count++;
if (Count % TIMES === 0) {
return 'error';
}
}
}
function process() {
var item, scale, result;
doc.selection = null;
// make a copy of the shape to work on
item = pth.duplicate(workLay, ElementPlacement.PLACEATBEGINNING);
// make sure the path is closed as we want the aligh stroke to inside
closeAllPath(item);
item = app.selection[0];
// apply the "aligh stroke to inside" graphicStyle by name
doc.graphicStyles.getByName('stroke inside').applyTo(item);
// scale works out how thick the stroke shold be
scale = Math.min(item.height, item.width) / 10 * per;
// this function is needed as CompoundPath can not be stroked with script!!!
strokeComPath(item, scale);
//$.writeln("scale: " + scale + ' ' + per);
app.executeMenuCommand('OffsetPath v22'); // = Outline Stroke
item = doc.selection[0];
if (item.typename === "CompoundPathItem") {
// if a CompoundPathItem selected, this will always be true
result = compareArea(item);
if (result != false) {
addCenterCircle(result);
item.remove();
return true
} else {
return false
}
} else {
// shape filled completely, reduced stroke and retry
item.remove();
return false;
}
}
function compareArea(compoundPath) {
var pi = compoundPath.pathItems,
l = pi.length,
i,
totalArea = 0,
tmpArr = [],
pthArea;
for (i = l - 1; i >= 0; i--) {
pthArea = Math.abs(Math.round(pi.area));
// hack of array.indexOf, should be replaced with real
var isOrigen = areaArr.toString().indexOf('' + pthArea);
//$.writeln ('path ' + i +' : ' + pthArea + ' ' + isOrigen)
if (isOrigen === -1) { // new path
totalArea = totalArea + pthArea;
tmpArr.push(pi);
}
}
$.writeln(totalArea)
if (totalArea < areaArr[0] * AREA_PER && totalArea > 0) { // the size is ok
tmpArr.sort(function(a, b) {
return Math.abs(b.area) - Math.abs(a.area)
})
return tmpArr[0]
} else if (totalArea === 0) { //stroke width is too big
compoundPath.remove();
per = per - increment / 10;
return false;
} else { // totalArea >= AREA_PER, stroke width is too small
compoundPath.remove();
per = per + increment / 2;
return false;
}
}
function closeAllPath(compoundPath) {
var pi = compoundPath.pathItems,
l = pi.length,
i;
compoundPath.selected = true;
app.executeMenuCommand('noCompoundPath');
for (i = 0; i < l; i++) {
app.selection.closed = 1;
};
app.executeMenuCommand('compoundPath');
}
function strokeComPath(compoundPath, weight) {
var pi = compoundPath.pathItems,
l = pi.length,
i = 0;
for (; i < l; i++) {
pi.strokeWidth = weight;
};
}
function logAreas(compoundPath) {
var pi = compoundPath.pathItems,
l = pi.length,
i,
totalArea = 0,
areaArr = [],
pthArea;
for (i = l - 1; i >= 0; i--) {
pthArea = pi.area;
totalArea = totalArea + pthArea;
// log 3 value as the outline stroke path may differ with origin
areaArr.push(Math.abs(Math.round(pthArea)));
areaArr.push(Math.abs(Math.round(pthArea)) - 1);
areaArr.push(Math.abs(Math.round(pthArea)) + 1);
}
areaArr.unshift(Math.abs(totalArea));
$.writeln(Math.abs(totalArea));
return areaArr
}
function addCenterCircle(pth) {
var gb = pth.position,
radius = 5;
center = [gb[0] + pth.width / 2 - radius, gb[1] - pth.height / 2 + radius];
doc.pathItems.ellipse(center[1], center[0], 10, 10);
}
Copy link to clipboard
Copied
Hello all, here comes the final version.
This time "offset path effect" been used. I thought this is a possible way at beginning, but never give it a try, until today.
It deal with both Path and Compound Path. Test the sample document, it only took 31 seconds.
Copy link to clipboard
Copied
Moluapple, this script works amazing! I tryed another file with many many objects, and it was pretty fast to do!
Thank you very much for your work! Qwertyfly thank u too for big beggining of this hard task!
I think, next step to improve, will be resizing numbers "pt" size in very small objects. It will be perfect to use, can u try?
Copy link to clipboard
Copied
So, does set the minimize font size to 4 pt fit your requirement? if size is too small, will be hard to recognize isn't it?
Above link has been updated, and big thanks to Hiroyuki Sato (@shspage) for his talent modify which lead to a more precise result.
As I have set the TIMES to 100, and other settings adjusted, It will use more time. The same document, this time 2'14''.
Copy link to clipboard
Copied
Moluapple, thanks a lot, 4 pt is enouth!
I am trying to start updated script on, but getting this error. Can u take a look please?
Copy link to clipboard
Copied
Sorry to say, that means your need to quit and relaunch Illustrator.
Copy link to clipboard
Copied
I did it again and again, not helps. Then i comment that string, no errors, but i don't think that it's good idea.
Copy link to clipboard
Copied
It seems depends on file, and layers cound, on file wich have less objects and layers no errors. I am trying to test some.
Copy link to clipboard
Copied
Should we wait another versions from qwertyfly and CarlosCanto?
I think the last version is very good, but not perfect, so will be glad to see another, if it exist.
Copy link to clipboard
Copied
Dear illustrator script ninjas! Thank u again for great work! I have a new little entreaty if u can help to modifty and check compared size of color's all object and show it on layer name.
For example area seize in square centimeters it can be like:
Layer 1 Num:1 Size: 0,5 cm
Layer 2 Num:2 Size: 2 cm
Is it real?
entreaty