Skip to main content
Participant
October 13, 2025
Answered

Convert mesh to normal paths

  • October 13, 2025
  • 1 reply
  • 810 views

I am pretty new to using scripts in Illustrator. I have a script that I would like to run in Illustrator. However, when I go to File > Scripts > Other Script and attempt to select my plain text file named SimplifyMeshToLines_Separated.jsx, the file is greyed out. If I save this file to the folder containing the Illustrator Scripts, it fails to show up in the list of Scripts. What am I doing wrong here? Thanks.

Correct answer m1b

Hi Mark, thanks for giving that a try. No, I could not get it to work either. I also tried this one (see attached), but I am not sure this works either (it is actually still running on my machine after quite a bit of time). Are you aware of any method to "convert" a mesh into a simplified line drawing?


Hi @chadulus2, I've written a script that works with the idea I mentioned earlier—saving as an Illustrator version 8 file. It works because it reads the plain text coordinates directly from the saved file and then draws them into the document. Each patch of the mesh is a path item.

 

Give it a try and let me know if it helps in your case.

- Mark

/**
 * @file Draw Geometry Of MeshItems.js
 *
 * Will add a path item for each patch
 * of each mesh item in the document.
 *
 * Note: document must be saved before
 * running script.
 *
 * @author m1b
 * @version 2025-10-18
 * @discussion https://community.adobe.com/t5/illustrator-discussions/greyed-out-script/m-p/15545562
 */
(function () {

    var settings = {
        drawAsPatches: false,
        layerName: 'meshes',
        showUI: true,
    };

    if (0 === app.documents.length)
        return alert('Please open a document and try again.');

    var doc = app.activeDocument;

    if (!doc.saved)
        return alert('Please save document and try again.');

    if (settings.showUI) {

        var result = ui(settings);

        if (2 === result)
            // user cancelled
            return;

    }

    // how to draw the mesh geometry
    settings.drawMesh = settings.drawAsPatches ? drawMeshAsPatches : drawMeshAsGridLines;

    // save temp document as AI version 8 (plain text)
    var original = doc.fullName;
    var so = new IllustratorSaveOptions();
    so.compatibility = Compatibility.ILLUSTRATOR8;
    so.compressed = true;
    var tempFile = File(Folder.temp + '/tmp.ai');
    doc.saveAs(tempFile, so);
    doc.close();

    // restore original document
    app.open(original);
    doc = app.activeDocument;

    // read the mesh coords
    var meshes = readMeshData(tempFile);

    // draw the meshes as path items
    var pathItems = [];

    // get a safe layer to draw to
    var layer = getThing(doc, 'name', settings.layerName) || doc.layers.add();
    layer.name = settings.layerName;
    layer.visible = true;
    layer.locked = false;

    // draw the meshes
    for (var i = 0; i < meshes.length; i++) {

        var group = layer.groupItems.add();
        group.name = 'mesh ' + (i + 1);

        pathItems = pathItems.concat(settings.drawMesh(group, meshes[i]));

    }

    if (0 === pathItems.length)
        alert('No mesh items found in document.');

})();

/**
 * Extract MeshItem coordinates from an Illustrator v8 file.
 * Will return an array containing mesh data for each mesh found.
 * @author m1b
 * @version 2025-10-18
 * @param {File} file - the file to read.
 * @returns {Array<Array<patch>>} - the mesh data objects.
 */
function readMeshData(file) {

    const MATCH_PATCH_START = /%_\[([^\]]+)\] \/P X#/;
    const MATCH_PATCH_END = /%_\/E X#/;
    const MATCH_PATCH_COORDS = /%_\[([^\]]+)\] \/N X#/;

    if (!file.exists)
        return;

    file.open('r');

    var meshes = [];
    var mesh;
    var patch;
    var line;
    var match;
    var coords;

    while (!file.eof) {

        line = file.readln();
        match = line.match(MATCH_PATCH_START);

        if (
            null !== match
            && 2 === match.length
        ) {
            // start new patch
            patch = { name: match[1], rowIndex: -1, columnIndex: -1, points: [] };

            var parts = patch.name.split(' ');

            if (2 !== parts.length)
                continue;

            patch.rowIndex = Number(parts[0]);
            patch.columnIndex = Number(parts[1]);

            if (
                isNaN(patch.rowIndex)
                || isNaN(patch.columnIndex)
            )
                continue;

            if (
                0 === patch.rowIndex
                && 0 === patch.columnIndex
            ) {
                // start new mesh
                mesh = [];
                meshes.push(mesh);
            }

            mesh.push(patch);
            continue;

        }

        if (!patch)
            // nothing
            continue;

        var match = line.match(MATCH_PATCH_COORDS);

        if (
            null === match
            || 2 > match.length
        ) {

            if (MATCH_PATCH_END.test(line))
                // end of patch
                patch = undefined;

            // part of a patch, but not geometry;
            continue;

        }

        var coords = match[1].split(' ');
        // start new point
        var numbers = [];

        for (var i = 0; i < coords.length; i++) {
            numbers.push(Number(coords[i]));
        }

        var len = numbers.length;

        patch.points.push({
            anchor: [numbers[len - 8], numbers[len - 7]],
            leftDirection: [numbers[len - 3], numbers[len - 2]],
            rightDirection: [numbers[len - 6], numbers[len - 5]],
        });

    }

    return meshes;

};

/**
 * Draws mesh patches.
 * @author m1b
 * @version 2025-10-18
 * @param {Document|Layer|GroupItem} container - the location of the new path items.
 * @param {Object} patches - patch objects { name:"", points:[] }.
 * @returns {Array<PathItem>}
 */
function drawMeshAsPatches(container, patches) {

    if (!container.hasOwnProperty('pathItems'))
        throw new Error('drawMesh: bad `container` supplied.');

    var pathItems = [];

    for (var i = 0; i < patches.length; i++) {

        var patch = patches[i];
        var pathItem = drawPath(container, patch.points, patch.name);
        pathItem.closed = true;
        pathItems.push(pathItem);

    }

    return pathItems;

};

/**
 * Draw mesh grid lines (rows and columns).
 * @author m1b
 * @version 2025-10-18
 * @param {Document|Layer|GroupItem} [container] - the container to draw in.
 * @param {Array<Object>} patches - array of patches {name: points:}.
 * @returns {Array<PathItem>}
 */
function drawMeshAsGridLines(container, patches) {

    var map = [];
    var maxRow = 0;
    var maxCol = 0;

    // parse patch names
    for (var i = 0; i < patches.length; i++) {

        var p = patches[i];

        if (!map[p.rowIndex])
            map[p.rowIndex] = [];

        map[p.rowIndex][p.columnIndex] = p;

        if (p.rowIndex > maxRow)
            maxRow = p.rowIndex;

        if (p.columnIndex > maxCol)
            maxCol = p.columnIndex;

    }

    var rowCount = maxRow + 1;
    var columnCount = maxCol + 1;

    // draw the lines
    var pathItems = [];
    var linePts = [];

    /* ------------------ *
     *  HORIZONTAL LINES  *
     * ------------------ */

    for (var r = 0; r < rowCount; r++) {

        for (var c = 0; c < columnCount; c++) {

            var patch = map[r][c];

            if (!linePts[c]) {
                linePts[c] = [];
            }

            if (!linePts[c + 1]) {
                linePts[c + 1] = [];
            }

            if (0 === c) {

                // right side of patch
                if (0 === r)
                    linePts[c][r] = {
                        anchor: patch.points[0].anchor,
                        leftDirection: patch.points[0].leftDirection,
                        rightDirection: patch.points[0].rightDirection,
                        pointType: PointType.CORNER,
                    };

                else
                    // fix join
                    linePts[c][r].rightDirection = patch.points[0].rightDirection;

                linePts[c][r + 1] = {
                    anchor: patch.points[1].anchor,
                    leftDirection: patch.points[1].leftDirection,
                    rightDirection: patch.points[1].rightDirection,
                    pointType: PointType.CORNER,
                };

            }

            // left side of patch
            if (0 === r)
                linePts[c + 1][r] = {
                    anchor: patch.points[3].anchor,
                    leftDirection: patch.points[3].rightDirection,
                    rightDirection: patch.points[3].leftDirection,
                    pointType: PointType.CORNER,
                };

            else
                // fix join
                linePts[c + 1][r].rightDirection = patch.points[3].leftDirection;

            linePts[c + 1][r + 1] = {
                anchor: patch.points[2].anchor,
                leftDirection: patch.points[2].rightDirection,
                rightDirection: patch.points[2].leftDirection,
                pointType: PointType.CORNER,
            };

        }

    }

    // draw horizontal lines
    for (var i = 0; i < linePts.length; i++)
        pathItems.push(drawPath(container, linePts[i], 'row ' + (i + 1)));


    /* ----------------- *
     *  VERTICAL LINES   *
     * ----------------- */
    var linePts = [];

    for (var c = 0; c < columnCount; c++) {

        for (var r = 0; r < rowCount; r++) {

            var patch = map[r][c];

            if (!linePts[r]) {
                linePts[r] = [];
            }

            if (!linePts[r + 1]) {
                linePts[r + 1] = [];
            }

            if (0 === r) {

                // bottom edge of patch
                if (0 === c)

                    linePts[r][c] = {
                        anchor: patch.points[0].anchor,
                        leftDirection: patch.points[0].rightDirection,
                        rightDirection: patch.points[0].leftDirection,
                        pointType: PointType.CORNER,
                    };

                else
                    // fix join
                    linePts[r][c].rightDirection = patch.points[0].leftDirection;

                linePts[r][c + 1] = {
                    anchor: patch.points[3].anchor,
                    leftDirection: patch.points[3].rightDirection,
                    rightDirection: patch.points[3].leftDirection,
                    pointType: PointType.CORNER,
                };

            }

            // top edge of patch
            if (0 === c)

                linePts[r + 1][c] = {
                    anchor: patch.points[1].anchor,
                    leftDirection: patch.points[1].leftDirection,
                    rightDirection: patch.points[1].rightDirection,
                    pointType: PointType.CORNER,
                };

            else
                // fix join
                linePts[r + 1][c].rightDirection = patch.points[1].rightDirection;

            linePts[r + 1][c + 1] = {
                anchor: patch.points[2].anchor,
                leftDirection: patch.points[2].leftDirection,
                rightDirection: patch.points[2].rightDirection,
                pointType: PointType.CORNER,
            };

        }

    }

    // draw vertical lines
    for (var i = 0; i < linePts.length; i++)
        pathItems.push(drawPath(container, linePts[i], 'column ' + (i + 1)));

    return pathItems;

};

/**
 * Draws a path of `points`;
 * @author m1b
 * @version 2025-10-18
 * @param {Document|Layer|GroupItem} [container] - the container to draw in.
 * @param {Array<PathPoints>} points - an array of points {anchor: leftDirection: rightDirection: pointType?:}.
 * @param {String} [name] - the path item name (default: none).
 * @returns {PathItem}
 */
function drawPath(container, points, name) {

    var path = newEmptyPathItem(container, name);

    for (var i = 0; i < points.length; i++) {

        var p = path.pathPoints.add();

        p.anchor = points[i].anchor;
        p.leftDirection = points[i].leftDirection;
        p.rightDirection = points[i].rightDirection;
        p.pointType = points.pointType || PointType.CORNER;

    }

    return path;

};

/**
 * Returns a new empty path item.
 * @author m1b
 * @version 2025-10-16
 * @param {Document|Layer|GroupItem} container - the location of the new path items.
 * @param {String} [name] - the name of the item (default: none).
 * @returns {PathItem}
 */
function newEmptyPathItem(container, name) {
    var item = container.pathItems.add();
    item.name = name || '';
    item.filled = false;
    item.stroked = true;
    return item;
};

/**
 * Returns a thing with matching property.
 * If `key` is undefined, evaluate the object itself.
 * @author m1b
 * @version 2024-04-21
 * @param {Array|Collection} things - the things to look through.
 * @param {String} [key] - the property name (default: undefined).
 * @param {*} value - the value to match.
 */
function getThing(things, key, value) {

    for (var i = 0, obj; i < things.length; i++)
        if ((undefined == key ? things[i] : things[i][key]) == value)
            return things[i];

};

/**
 * User interface for this script. Will update the `settings` object.
 * @author m1b
 * @version 2025-10-18
 * @param {Object} settings - the script settings.
 * @returns {1|2}
 */
function ui(settings) {

    var w = new Window("dialog { text:'Draw MeshItem Geometry' }");

    var group = w.add('group {orientation:"column", alignment:["fill","fill"], alignChildren:["left","fill"], margins:[10,10,10,10] }');

    var radioButtons = group.add('group {orientation:"row", margins:[10,10,10,10] }'),
        asPatchesRadio = radioButtons.add('radiobutton {text:"Draw mesh as patches"}'),
        asLinesRadio = radioButtons.add('radiobutton {text:"Draw mesh as lines"}');

    var bottomUI = w.add('group {orientation:"row", alignment:["fill","top"], margins: [0,20,0,0] }'),
        buttons = bottomUI.add('group {orientation:"row", alignment:["right","top"], alignChildren:"right" }'),
        cancelButton = buttons.add('button', undefined, 'Cancel', { name: 'cancel' }),
        drawButton = buttons.add('button', undefined, 'Draw', { name: 'ok' });

    (settings.drawAsPatches ? asPatchesRadio : asLinesRadio).value = true;

    drawButton.onClick = function () {
        settings.drawAsPatches = asPatchesRadio.value;
        w.close(1);
    };

    w.center();
    return w.show();

};

Edit 2025-10-18: added capability to draw geometry as grid lines, as well as patches.

1 reply

Monika Gause
Community Expert
Community Expert
October 13, 2025

Can you possibly share the jsx file?

chadulus2Author
Participant
October 13, 2025

For some reason it would not let me upload the .jsx file, however, I have attached a plain text version.

chadulus2Author
Participant
October 17, 2025

fantastic Mark!


Thanks Mark!