mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
10 KiB
10 KiB
/* With This Script it is possible to make boolean Operations on Shapes.
See documentation for more details: https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
*/
const PolyBool = ea.getPolybool();
const polyboolAction = await utils.suggester(["union (a + b)", "intersect (a && b)", "diffrence (a - b)", "reversed diffrence (b - a)", "xor"], [
PolyBool.union, PolyBool.intersect, PolyBool.difference, PolyBool.differenceRev, PolyBool.xor
], "What would you like todo with the object");
const elements = ea.getViewSelectedElements();
const shadowClones = elements.filter(element => element.isShadowCloneOf);
shadowClones.forEach(shadowClone => {
const shadowCloneIndex = elements.findIndex(element => element.id = shadowClone.isShadowCloneOf);
if (shadowCloneIndex == -1) return;
elements[shadowCloneIndex].backgroundColor = shadowClone.backgroundColor;
})
const borderElements = elements.filter(element => !element.isShadowCloneOf);
groups = ea.getMaximumGroups(borderElements);
groups = groups.map((group) => group.sort((a, b) => RankElement(b) - RankElement(a)));
groups.sort((a, b) => RankElement(b[0]) - RankElement(a[0]));
ea.style.strokeColor = groups[0][0].strokeColor;
ea.style.backgroundColor = groups[0][0].backgroundColor;
ea.style.fillStyle = groups[0][0].fillStyle;
ea.style.strokeWidth = groups[0][0].strokeWidth;
ea.style.strokeStyle = groups[0][0].strokeStyle;
ea.style.roughness = groups[0][0].roughness;
ea.style.opacity = groups[0][0].opacity;
const basePolygons = groups.shift().map(element => traceElement(element));
const toolPolygons = groups.flatMap(group => group.map(element => traceElement(element)));
const result = polyboolAction({
regions: basePolygons,
inverted: false
}, {
regions: toolPolygons,
inverted: false
});
const polygonHierachy = subordinateInnerPolygons(result.regions);
drawPolygonHierachy(polygonHierachy);
ea.deleteViewElements(elements);
ea.addElementsToView(false,false,true);
// return;
function traceElement(element) {
const diamondPath = (diamond) => [
SxVEC(1/2, [0, diamond.height]),
SxVEC(1/2, [diamond.width, 0]),
addVec([SxVEC(1/2, [0, diamond.height]), ([diamond.width, 0])]),
addVec([SxVEC(1/2, [diamond.width, 0]), ([0, diamond.height])]),
SxVEC(1/2, [0, diamond.height])
];
const rectanglePath = (rectangle) => [
[0,0],
[0, rectangle.height],
[rectangle.width, rectangle.height],
[rectangle.width, 0],
[0, 0]
]
const ellipsePath = (ellipse) => {
const angle = ellipse.angle;
const width = ellipse.width;
const height = ellipse.height;
const ellipseAtPoint = (t) => {
const spanningVector = [width/2*Math.cos(t), height/2*Math.sin(t)];
const baseVector = [width/2, height/2];
return addVec([spanningVector, baseVector]);
}
let points = [];
step = (2*Math.PI)/64
for (let t = 0; t < 2*Math.PI; t = t + step) {
points.push(ellipseAtPoint(t));
}
return points;
}
let polygon;
let correctForPolygon = [0, 0];
switch (element.type) {
case "diamond":
polygon = diamondPath(element);
break;
case "rectangle":
polygon = rectanglePath(element);
break;
case "ellipse":
polygon = ellipsePath(element);
break;
case "line":
case "arrow":
if (element.angle != 0) {
let smallestX = 0;
let smallestY = 0;
element.points.forEach(point => {
if (point[0] < smallestX) smallestX = point[0];
if (point[1] < smallestY) smallestY = point[1];
});
polygon = element.points.map(point => {
return [
point[0] -= smallestX,
point[1] -= smallestY
];
});
correctForPolygon = [smallestX, smallestY];
break;
}
if (element.roundness) {
new Notice("This script does not work with curved lines or arrows yet!");
return [];
}
polygon = element.points;
default:
break;
}
if (element.angle == 0) return polygon.map(v => addVec([v, [element.x, element.y]]));
polygon = polygon.map(v => addVec([v, SxVEC(-1/2, [element.width, element.height])]));
polygon = rotateVectorsByAngle(polygon, element.angle);
return polygon.map(v => addVec([v, [element.x, element.y], SxVEC(1/2, [element.width, element.height]), correctForPolygon]));
}
function RankElement(element) {
let score = 0;
const backgroundRank = [
"dashed",
"none",
"hachure",
"zigzag",
"zigzag-line",
"cross-hatch",
"solid"
]
score += (backgroundRank.findIndex((fillStyle) => fillStyle == element.fillStyle) + 1) * 10;
if (element.backgroundColor == "transparent") score -= 100;
if (element.points && getVectorLength(element.points[element.points.length - 1]) > 8) score -= 100;
if (score < 0) score = 0;
score += element.opacity / 100;
return score;
}
function drawPolygonHierachy(polygonHierachy) {
const backgroundColor = ea.style.backgroundColor;
const strokeColor = ea.style.strokeColor;
const setInnerStyle = () => {
ea.style.backgroundColor = backgroundColor;
ea.style.strokeColor = "transparent";
}
const setBorderStyle = () => {
ea.style.backgroundColor = "transparent";
ea.style.strokeColor = strokeColor;
}
const setFilledStyle = () => {
ea.style.backgroundColor = backgroundColor;
ea.style.strokeColor = strokeColor;
}
let gatheredIds = [];
polygonHierachy.forEach(polygon => {
let path = polygon.path;
path.push(polygon.path[0]);
if (polygon.innerPolygons.length === 0) {
setFilledStyle();
ea.addLine(path);
return;
}
const outerBorder = path;
const innerPolygons = addInnerPolygons(polygon.innerPolygons);
path = path.concat(innerPolygons.backgroundPath);
path.push(polygon.path[0]);
setInnerStyle();
const backgroundId = ea.addLine(path);
setBorderStyle();
const outerBorderId = ea.addLine(outerBorder)
const background = ea.getElement(backgroundId);
background.isShadowCloneOf = outerBorderId;
const innerBorderIds = innerPolygons.borderPaths.map(path => ea.addLine(path));
const allIds = [innerBorderIds, outerBorderId, backgroundId].flat();
gatheredIds = gatheredIds.concat(allIds);
ea.addToGroup(allIds);
});
ea.addToGroup(gatheredIds);
}
function addInnerPolygons(polygonHierachy) {
let firstPath = [];
let secondPath = [];
let borderPaths = [];
polygonHierachy.forEach(polygon => {
let path = polygon.path;
path.push(polygon.path[0]);
borderPaths.push(path);
firstPath = firstPath.concat(path);
secondPath.push(polygon.path[0]);
});
return {
backgroundPath: firstPath.concat(secondPath),
borderPaths: borderPaths
};
}
function subordinateInnerPolygons(polygons) {
const polygonObjectPrototype = (polygon) => {
return {
path: polygon,
innerPolygons: []
};
}
const insertPolygonIntoHierachy = (polygon, hierarchy) => {
for (let i = 0; i < hierarchy.length; i++) {
const polygonObject = hierarchy[i];
let inside = null;
let pointIndex = 0;
do {
inside = pointInPolygon(polygon[pointIndex], polygonObject.path);
pointIndex++
} while (inside === null);
if (inside) {
hierarchy[i].innerPolygons = insertPolygonIntoHierachy(polygon, hierarchy[i].innerPolygons);
return hierarchy;
}
}
polygon = polygonObjectPrototype(polygon);
for (let i = 0; i < hierarchy.length; i++) {
const polygonObject = hierarchy[i];
let inside = null;
let pointIndex = 0;
do {
inside = pointInPolygon(polygonObject.path[pointIndex], polygon.path);
pointIndex++
} while (inside === null);
if (inside) {
polygon.innerPolygons.push(hierarchy.splice(i, 1)[0]);
i--;
}
}
hierarchy.push(polygon);
return hierarchy;
}
let polygonHierachy = [];
polygons.forEach(polygon => {
polygonHierachy = insertPolygonIntoHierachy(polygon, polygonHierachy);
})
return polygonHierachy;
}
/**
* Checks if the given point lays in the polygon
* @param point array [x, y]
* @param polygon array [[x, y], ...]
* @returns true if inside, false if not, null if the point is on one of the polygons vertecies
*/
function pointInPolygon(point, polygon) {
const x = point[0];
const y = point[1];
let inside = false;
// odd even test if point is in polygon
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i][0];
const yi = polygon[i][1];
const xj = polygon[j][0];
const yj = polygon[j][1];
const intersect =
yi > y !== yj > y &&
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) {
inside = !inside;
}
if ((x === xi && y === yi) || (x === xj && y === yj)) {
return null;
}
}
return inside;
}
function getVectorLength(vector) {
return Math.sqrt(vector[0]**2+vector[1]**2);
}
/**
* Adds two Vectors together
*/
function addVec(vectors) {
return vectors.reduce((acc, vec) => [acc[0] + vec[0], acc[1] + vec[1]], [0, 0]);
}
/**
* Returns the negative of the vector
*/
function negVec(vector) {
return [-vector[0], -vector[1]];
}
/**
* Multiplies Vector with a scalar
*/
function SxVEC(scalar, vector) {
return [vector[0] * scalar, vector[1] * scalar];
}
function rotateVector (vec, ang) {
var cos = Math.cos(ang);
var sin = Math.sin(ang);
return [vec[0] * cos - vec[1] * sin, vec[0] * sin + vec[1] * cos];
}
function rotateVectorsByAngle(vectors, angle) {
const cosAngle = Math.cos(angle);
const sinAngle = Math.sin(angle);
const rotationMatrix = [
[cosAngle, -sinAngle],
[sinAngle, cosAngle]
];
return applyTranformationMatrix(vectors, rotationMatrix);
}
function applyTranformationMatrix(vectors, transformationMatrix) {
const result = [];
for (const vector of vectors) {
const x = vector[0];
const y = vector[1];
const newX = transformationMatrix[0][0] * x + transformationMatrix[0][1] * y;
const newY = transformationMatrix[1][0] * x + transformationMatrix[1][1] * y;
result.push([newX, newY]);
}
return result;
}