Answered
Bonjour Marc, je me suis permis (hier soir) de modifier votre fonction unrotate().
pour gérer les groupes, tracés transparents et les masques.
Cela semble fonctionner?
function unrotate(item) {
var grp = false, trs = false;
var accumRotation = undefined,
rotationTag;
if (item.hasOwnProperty('tags')) {
if (item.typename == "GroupItem" || item.typename == "CompoundPathItem") {
if (item.typename == "CompoundPathItem") {
rotationTag = getThing(item.pathItems[0].tags, 'name', 'BBAccumRotation');
trs = true;
}
else {
rotationTag = getThing(item.pageItems[0].tags, 'name', 'BBAccumRotation');
grp = true;
}
}
else rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
if (rotationTag)
accumRotation = rotationTag.value * 180 / Math.PI;
}
if (
undefined == accumRotation
&& item.hasOwnProperty('matrix')
)
// derive rotation from the matrix
accumRotation = getRotationFromMatrix(item.matrix);
if (undefined == accumRotation)
return;
// rotate the item
item.rotate(- accumRotation);
if (rotationTag)
// update tag
var pi;
if (grp)
for (var i = 0; i < item.pageItems.length; i++ ){
pi = item.pageItems[i];
rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) {rotationTag.value = 0;}
}
}
if (trs) {
pi = item.pathItems[0];
rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) {rotationTag.value = 0;}
rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) {rotationTag.value = 0;}
}
if (!trs && !grp) rotationTag.value = 0;
};
René
Okay @vectora98504848 here is the script again, now with René's improved function. Let us know how it goes!
- Mark
/**
* @file Unrotate Selected Items 2.js
*
* Makes an attempt to remove any recorded rotation
* that can be derived from the selected items.
* Finds rotation either in the item's BBAccumRotation
* tag or the item's matrix.
*
* @author m1b
* @version 2025-02-17
* @discussion https://community.adobe.com/t5/illustrator-discussions/script-to-return-or-restore-rotation-of-multiple-selected-objects/m-p/15146692
*/
(function () {
var doc = app.activeDocument;
var items = getItems({
from: doc.selection,
getPageItems: true,
getGroupItems: true,
});
if (0 === items.length)
return alert('Please select some items to rotate and try again.');
for (var i = 0; i < items.length; i++)
unrotate2(items[i]);
})();
/**
* Attempt to return a page item to unrotated state.
* Modified by renél80416020 to apply rotation
* to group items and compound path items correctly.
* Important: will only work if the item has a valid
* BBAccumRotation tag or a matrix that is ammenable
* to derivation of the rotation.
* @author m1b and renél80416020
* @version 2025-02-14
* @param {PageItem} item - the item to unrotate.
* @return {Number} - the amount of rotation, in degrees.
*/
function unrotate2(item) {
var grp = false, trs = false;
var accumRotation = undefined,
rotationTag;
if (item.hasOwnProperty('tags')) {
if (item.typename == "GroupItem" || item.typename == "CompoundPathItem") {
if (item.typename == "CompoundPathItem") {
rotationTag = getThing(item.pathItems[0].tags, 'name', 'BBAccumRotation');
trs = true;
}
else {
rotationTag = getThing(item.pageItems[0].tags, 'name', 'BBAccumRotation');
grp = true;
}
}
else rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
if (rotationTag)
accumRotation = rotationTag.value * 180 / Math.PI;
}
if (
undefined == accumRotation
&& item.hasOwnProperty('matrix')
)
// derive rotation from the matrix
accumRotation = getRotationFromMatrix(item.matrix);
if (undefined == accumRotation)
return;
// rotate the item
item.rotate(- accumRotation);
if (rotationTag) {
// update tag
var pi;
if (grp)
for (var i = 0; i < item.pageItems.length; i++) {
pi = item.pageItems[i];
rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) { rotationTag.value = 0; }
}
if (trs) {
pi = item.pathItems[0];
rotationTag = getThing(pi.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) { rotationTag.value = 0; }
rotationTag = getThing(item.tags, 'name', 'BBAccumRotation');
if (rotationTag != undefined) { rotationTag.value = 0; }
}
if (!trs && !grp)
rotationTag.value = 0;
}
};
/**
* 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];
};
/**
* Returns the rotation amount, in degrees,
* of the given (not skewed) matrix.
* @author m1b
* @version 2023-10-20
* @param {Matrix} matrix - an Illustrator Matrix.
* @returns {Number}
*/
function getRotationFromMatrix(matrix) {
if (!matrix.hasOwnProperty('mValueA'))
throw new Error('getRotationFromMatrix: bad `matrix` supplied.');
// scaling factors
var scaleX = Math.sqrt(matrix.mValueA * matrix.mValueA + matrix.mValueC * matrix.mValueC);
// scaleY = Math.sqrt(matrix.mValueB * matrix.mValueB + matrix.mValueD * matrix.mValueD);
// rotation angle
var radians = Math.acos(matrix.mValueA / scaleX),
degrees = radians * (180 / Math.PI);
return Math.round(degrees * 1000) / 1000;
};
/** ------------------------------------------------------------------- *
* GET ITEMS *
* -------------------------------------------------------------------- *
* @author m1b *
* @version 2024-03-01 *
* -------------------------------------------------------------------- *
* Collects page items from a `from` source, eg. a Document, Layer, *
* GroupItem, or Array. Will look inside group items up to `maxDepth`. *
* Search can be filtered using `filter` function. Note that the *
* filter function is evaluated last in the filtering process. *
* -------------------------------------------------------------------- *
* Example 1. Get all items in document: *
* *
* var myItems = getItems({ from: app.activeDocument }); *
* *
* -------------------------------------------------------------------- *
* Example 2. Get all selected items except groups: *
* *
* var myItems = getItems({ *
* from: app.activeDocument.selection, *
* getGroupItems: false, *
* }); *
* *
* -------------------------------------------------------------------- *
* Example 3. Using `filter` function to choose item type: *
* *
* var myItems = getItems({ *
* from: app.activeDocument, *
* filter: function (item) { *
* return ( *
* 'PathItem' === item.typename *
* || 'CompoundPathItem' === item.typename *
* ); *
* } *
* }); *
* *
* -------------------------------------------------------------------- *
* Example 4. Using `filter` function: *
* *
* var myItems = getItems({ *
* from: app.activeDocument, *
* filter: onlyPngLinks *
* }); *
* *
* function onlyPngLinks(item, depth) { *
* return ( *
* 'PlacedItem' === item.typename *
* && '.png' === item.file.name.slice(-4).toLowerCase() *
* ); *
* }; *
* *
* -------------------------------------------------------------------- *
* Example 4. Using the `filter` function for custom collecting: *
* *
* This example bypasses the normal returned array and instead *
* captures items in an "external" array `itemsByDepth`. *
* *
* var itemsByDepth = []; *
* *
* function getItemsByDepth(item, depth) { *
* if (undefined == itemsByDepth[depth]) *
* itemsByDepth[depth] = []; *
* itemsByDepth[depth].push(item); *
* }; *
* *
* getItems({ *
* from: app.activeDocument, *
* filter: getItemsByDepth *
* }); *
* *
* -------------------------------------------------------------------- *
* @param {Object} options - parameters
* @param {PageItem|Array<PageItem>|Document|Layer} options.from - the thing(s) to look in, eg. a selection.
* @param {Function} [options.filter] - function that, given a found item, must return true (default: no filtering).
* @param {Boolean} [options.getPageItems] - whether to include page items in returned items (default: true).
* @param {Boolean} [options.getGroupItems] - whether to include GroupItems in returned items (default: true).
* @param {Boolean} [options.getLayers] - whether to include Layers in returned items (default: false).
* @param {Boolean} [options.getHiddenItems] - whether to include hidden items in returned items (default: true).
* @param {Boolean} [options.getLockedItems] - whether to include locked items in returned items (default: true).
* @param {Boolean} [options.getGuideItems] - whether to include guide items in returned items (default: false).
* @param {Number} [options.maxDepth] - deepest folder level (recursion depth limit) (default: 99).
* @param {Boolean} [options.returnFirstMatch] - whether to return only the first found item (default: false).
* @param {Number} [depth] - the current depth (private).
* @returns {Array|PageItem} - all the found items in a flat array, or the first found item if `returnFirstMatch`.
*/
function getItems(options, depth) {
// defaults
options = options || {};
var found = [],
depth = depth || 0,
items = options.from;
if (!options.initialized)
// once-off initialization
if (!initialize())
return [];
itemsLoop:
for (var i = 0, item, len = items.length; i < len; i++) {
item = items[i];
if (
false === excludeFilter(item)
&& true === includeFilter(item)
) {
// item found!
found.push(item);
if (options.returnFirstMatch)
break itemsLoop;
}
if (
'GroupItem' !== item.constructor.name
&& 'Layer' !== item.typename
)
// only items with children from here
continue itemsLoop;
if (
excludeHidden(item)
|| excludeLocked(item)
)
// don't look into excluded containers
continue itemsLoop;
if (depth >= options.maxDepth)
// don't go deeper
continue itemsLoop;
// set up for the next depth
options.from = item.pageItems;
// look inside
found = found.concat(getItems(options, depth + 1));
}
// this level done
if (true == options.returnFirstMatch)
return found[0];
else
return found;
/**
* Returns true when the item should be not be found.
* @param {PageItem|Layer} item
* @returns {Boolean}
*/
function excludeFilter(item) {
return (
isAlreadyFound(item)
// is hidden
|| excludeHidden(item)
// is locked
|| excludeLocked(item)
// is guide
|| (
false === options.getGuideItems
&& true === item.guides
)
// is layer
|| (
false === options.getLayers
&& 'Layer' === item.typename
)
// is group item
|| (
false === options.getGroupItems
&& 'GroupItem' === item.typename
)
// is page item
|| (
false === options.getPageItems
&& 'GroupItem' !== item.typename
&& undefined != item.uuid
)
);
};
/**
* Returns true when the item should be included.
* @param {PageItem|Layer} item
* @returns {Boolean}
*/
function includeFilter(item) {
return (
undefined == options.filter
|| options.filter(item, depth)
);
};
/**
* Returns true when the item should
* be excluded because it is hidden.
* @param {PageItem|Layer} item
* @returns {Boolean}
*/
function excludeHidden(item) {
return (
false === options.getHiddenItems
&& (
true === item.hidden
|| false === item.visible
)
);
};
/**
* Returns true when the item should
* be excluded because it is locked.
* @param {PageItem|Layer} item
* @returns {Boolean}
*/
function excludeLocked(item) {
return (
false === options.getLockedItems
&& true === item.locked
);
};
/**
* Returns true if item was already
* found, and marks item as found,
* to avoid finding same item twice.
* @param {PageItem|Layer} item
* @returns {Boolean}
*/
function isAlreadyFound(item) {
var uuid = item.hasOwnProperty('uuid')
? item.uuid
: item.typename + item.zOrderPosition,
isFound = !!options.isFound[uuid];
options.isFound[uuid] = true;
return isFound;
}
/**
* Returns the initialised `options` object.
* @returns {Object}
*/
function initialize() {
// make a new object, so we don't pollute the original
options = {
initialized: true,
depth: 0,
isFound: {},
filter: options.filter,
getPageItems: false !== options.getPageItems,
getGroupItems: false !== options.getGroupItems,
getLayers: true === options.getLayers,
getHiddenItems: false !== options.getHiddenItems,
getLockedItems: false !== options.getLockedItems,
getGuideItems: true === options.getGuideItems,
maxDepth: options.maxDepth,
returnFirstMatch: options.returnFirstMatch,
};
if (
undefined == options.maxDepth
|| !options.maxDepth instanceof Number
)
options.maxDepth = 99;
// items is a single layer
if ('Layer' === items.typename)
items = [items];
// items is a document
else if ('Document' === items.constructor.name) {
var layers = items.layers;
items = [];
for (var i = 0; i < layers.length; i++)
items.push(layers[i]);
}
else if ('Array' !== items.constructor.name)
items = [items];
return items.length > 0;
};
};
Sign up
Already have an account? Login
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inSign in to Adobe Community
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inEnter your E-mail address. We'll send you an e-mail with instructions to reset your password.
