@tscircuit/pcb-viewer 1.11.412 → 1.11.414
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +450 -235
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -948,9 +948,6 @@ import { createContext, useContext } from "react";
|
|
|
948
948
|
var RenderingEngineContext = createContext(null);
|
|
949
949
|
var useRenderingEngine = () => useContext(RenderingEngineContext);
|
|
950
950
|
|
|
951
|
-
// src/PCBViewer.tsx
|
|
952
|
-
import { applyEditEvents } from "@tscircuit/core";
|
|
953
|
-
|
|
954
951
|
// node_modules/@tscircuit/circuit-json-util/dist/index.js
|
|
955
952
|
import * as Soup from "circuit-json";
|
|
956
953
|
import * as Soup2 from "circuit-json";
|
|
@@ -8993,6 +8990,158 @@ var cjuIndexed = (soup, options = {}) => {
|
|
|
8993
8990
|
return suIndexed;
|
|
8994
8991
|
};
|
|
8995
8992
|
cjuIndexed.unparsed = cjuIndexed;
|
|
8993
|
+
var getQuarterTurns = (angleRadians) => Math.round(angleRadians / (Math.PI / 2));
|
|
8994
|
+
var insertionDirectionToVec = (direction) => {
|
|
8995
|
+
switch (direction) {
|
|
8996
|
+
case "from_left":
|
|
8997
|
+
return { x: -1, y: 0 };
|
|
8998
|
+
case "from_right":
|
|
8999
|
+
return { x: 1, y: 0 };
|
|
9000
|
+
case "from_top":
|
|
9001
|
+
return { x: 0, y: 1 };
|
|
9002
|
+
case "from_bottom":
|
|
9003
|
+
return { x: 0, y: -1 };
|
|
9004
|
+
}
|
|
9005
|
+
};
|
|
9006
|
+
var vecToInsertionDirection = ({
|
|
9007
|
+
x,
|
|
9008
|
+
y
|
|
9009
|
+
}) => {
|
|
9010
|
+
if (x > 0) return "from_right";
|
|
9011
|
+
if (x < 0) return "from_left";
|
|
9012
|
+
if (y > 0) return "from_top";
|
|
9013
|
+
return "from_bottom";
|
|
9014
|
+
};
|
|
9015
|
+
var transformInsertionDirection = (direction, opts) => {
|
|
9016
|
+
if (!direction) return direction;
|
|
9017
|
+
if (direction === "from_above" || direction === "from_below") {
|
|
9018
|
+
if (!opts.isFlipped) return direction;
|
|
9019
|
+
return direction === "from_above" ? "from_below" : "from_above";
|
|
9020
|
+
}
|
|
9021
|
+
let { x, y } = insertionDirectionToVec(direction);
|
|
9022
|
+
let quarterTurns = Math.round(opts.rotationDegrees / 90);
|
|
9023
|
+
while (quarterTurns > 0) {
|
|
9024
|
+
;
|
|
9025
|
+
[x, y] = [-y, x];
|
|
9026
|
+
quarterTurns--;
|
|
9027
|
+
}
|
|
9028
|
+
while (quarterTurns < 0) {
|
|
9029
|
+
;
|
|
9030
|
+
[x, y] = [y, -x];
|
|
9031
|
+
quarterTurns++;
|
|
9032
|
+
}
|
|
9033
|
+
if (opts.isFlipped) {
|
|
9034
|
+
y = -y;
|
|
9035
|
+
}
|
|
9036
|
+
return vecToInsertionDirection({ x, y });
|
|
9037
|
+
};
|
|
9038
|
+
var transformPCBElement = (elm, matrix2) => {
|
|
9039
|
+
const tsr = decomposeTSR(matrix2);
|
|
9040
|
+
const flipPadWidthHeight = Math.abs(getQuarterTurns(tsr.rotation.angle)) % 2 === 1;
|
|
9041
|
+
const rotationDegrees = tsr.rotation.angle / Math.PI * 180;
|
|
9042
|
+
const isFlipped = tsr.scale.sy < 0;
|
|
9043
|
+
if (elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "pcb_via" || elm.type === "pcb_smtpad" || elm.type === "pcb_solder_paste" || elm.type === "pcb_port") {
|
|
9044
|
+
const { x, y } = applyToPoint(matrix2, {
|
|
9045
|
+
x: Number(elm.x),
|
|
9046
|
+
y: Number(elm.y)
|
|
9047
|
+
});
|
|
9048
|
+
elm.x = x;
|
|
9049
|
+
elm.y = y;
|
|
9050
|
+
if (elm.type === "pcb_smtpad" && elm.shape === "polygon" && Array.isArray(elm.points)) {
|
|
9051
|
+
elm.points = elm.points.map((point2) => {
|
|
9052
|
+
const tp = applyToPoint(matrix2, { x: point2.x, y: point2.y });
|
|
9053
|
+
return {
|
|
9054
|
+
x: tp.x,
|
|
9055
|
+
y: tp.y
|
|
9056
|
+
};
|
|
9057
|
+
});
|
|
9058
|
+
}
|
|
9059
|
+
} else if (elm.type === "pcb_keepout" || elm.type === "pcb_board") {
|
|
9060
|
+
elm.center = applyToPoint(matrix2, elm.center);
|
|
9061
|
+
} else if (elm.type === "pcb_silkscreen_text" || elm.type === "pcb_fabrication_note_text" || elm.type === "pcb_note_text") {
|
|
9062
|
+
elm.anchor_position = applyToPoint(matrix2, elm.anchor_position);
|
|
9063
|
+
} else if (elm.type === "pcb_copper_text") {
|
|
9064
|
+
if (elm.anchor_position) {
|
|
9065
|
+
elm.anchor_position = applyToPoint(matrix2, elm.anchor_position);
|
|
9066
|
+
}
|
|
9067
|
+
} else if (elm.type === "pcb_courtyard_rect") {
|
|
9068
|
+
elm.center = applyToPoint(matrix2, elm.center);
|
|
9069
|
+
elm.ccw_rotation = ((elm.ccw_rotation ?? 0) + rotationDegrees) % 360;
|
|
9070
|
+
} else if (elm.type === "pcb_silkscreen_circle" || elm.type === "pcb_silkscreen_rect" || elm.type === "pcb_silkscreen_pill" || elm.type === "pcb_silkscreen_oval" || elm.type === "pcb_note_rect" || elm.type === "pcb_courtyard_circle") {
|
|
9071
|
+
elm.center = applyToPoint(matrix2, elm.center);
|
|
9072
|
+
} else if (elm.type === "pcb_component") {
|
|
9073
|
+
elm.center = applyToPoint(matrix2, elm.center);
|
|
9074
|
+
elm.rotation = elm.rotation + rotationDegrees;
|
|
9075
|
+
elm.rotation = elm.rotation % 360;
|
|
9076
|
+
if (elm.cable_insertion_center) {
|
|
9077
|
+
elm.cable_insertion_center = applyToPoint(
|
|
9078
|
+
matrix2,
|
|
9079
|
+
elm.cable_insertion_center
|
|
9080
|
+
);
|
|
9081
|
+
}
|
|
9082
|
+
elm.insertion_direction = transformInsertionDirection(
|
|
9083
|
+
elm.insertion_direction,
|
|
9084
|
+
{
|
|
9085
|
+
rotationDegrees,
|
|
9086
|
+
isFlipped
|
|
9087
|
+
}
|
|
9088
|
+
);
|
|
9089
|
+
if (flipPadWidthHeight) {
|
|
9090
|
+
;
|
|
9091
|
+
[elm.width, elm.height] = [elm.height, elm.width];
|
|
9092
|
+
}
|
|
9093
|
+
} else if (elm.type === "pcb_courtyard_outline") {
|
|
9094
|
+
elm.outline = elm.outline.map((p) => {
|
|
9095
|
+
const tp = applyToPoint(matrix2, p);
|
|
9096
|
+
p.x = tp.x;
|
|
9097
|
+
p.y = tp.y;
|
|
9098
|
+
return p;
|
|
9099
|
+
});
|
|
9100
|
+
} else if (elm.type === "pcb_courtyard_polygon") {
|
|
9101
|
+
elm.points = elm.points.map((p) => {
|
|
9102
|
+
const tp = applyToPoint(matrix2, p);
|
|
9103
|
+
p.x = tp.x;
|
|
9104
|
+
p.y = tp.y;
|
|
9105
|
+
return p;
|
|
9106
|
+
});
|
|
9107
|
+
} else if (elm.type === "pcb_trace") {
|
|
9108
|
+
elm.route = elm.route.map((rp) => {
|
|
9109
|
+
if (!("x" in rp)) {
|
|
9110
|
+
rp.start = applyToPoint(matrix2, rp.start);
|
|
9111
|
+
rp.end = applyToPoint(matrix2, rp.end);
|
|
9112
|
+
return rp;
|
|
9113
|
+
}
|
|
9114
|
+
const tp = applyToPoint(matrix2, rp);
|
|
9115
|
+
rp.x = tp.x;
|
|
9116
|
+
rp.y = tp.y;
|
|
9117
|
+
return rp;
|
|
9118
|
+
});
|
|
9119
|
+
} else if (elm.type === "pcb_silkscreen_path" || elm.type === "pcb_trace_hint" || elm.type === "pcb_fabrication_note_path" || elm.type === "pcb_note_path") {
|
|
9120
|
+
elm.route = elm.route.map((rp) => {
|
|
9121
|
+
const tp = applyToPoint(matrix2, rp);
|
|
9122
|
+
rp.x = tp.x;
|
|
9123
|
+
rp.y = tp.y;
|
|
9124
|
+
return rp;
|
|
9125
|
+
});
|
|
9126
|
+
} else if (elm.type === "pcb_silkscreen_line" || elm.type === "pcb_note_line") {
|
|
9127
|
+
const p1 = { x: elm.x1, y: elm.y1 };
|
|
9128
|
+
const p2 = { x: elm.x2, y: elm.y2 };
|
|
9129
|
+
const p1t = applyToPoint(matrix2, p1);
|
|
9130
|
+
const p2t = applyToPoint(matrix2, p2);
|
|
9131
|
+
elm.x1 = p1t.x;
|
|
9132
|
+
elm.y1 = p1t.y;
|
|
9133
|
+
elm.x2 = p2t.x;
|
|
9134
|
+
elm.y2 = p2t.y;
|
|
9135
|
+
} else if (elm.type === "cad_component") {
|
|
9136
|
+
const newPos = applyToPoint(matrix2, {
|
|
9137
|
+
x: elm.position.x,
|
|
9138
|
+
y: elm.position.y
|
|
9139
|
+
});
|
|
9140
|
+
elm.position.x = newPos.x;
|
|
9141
|
+
elm.position.y = newPos.y;
|
|
9142
|
+
}
|
|
9143
|
+
return elm;
|
|
9144
|
+
};
|
|
8996
9145
|
var getElementId = (elm) => {
|
|
8997
9146
|
const type = elm.type;
|
|
8998
9147
|
const id = elm[`${type}_id`];
|
|
@@ -9806,7 +9955,7 @@ var useMeasure_default = isBrowser && typeof window.ResizeObserver !== "undefine
|
|
|
9806
9955
|
};
|
|
9807
9956
|
|
|
9808
9957
|
// src/PCBViewer.tsx
|
|
9809
|
-
import { compose as compose9, scale as scale6, translate as
|
|
9958
|
+
import { compose as compose9, scale as scale6, translate as translate14 } from "transformation-matrix";
|
|
9810
9959
|
|
|
9811
9960
|
// node_modules/use-mouse-matrix-transform/dist/chunk-TGYMZPTI.js
|
|
9812
9961
|
import { compose as compose2, translate as translate6, scale } from "transformation-matrix";
|
|
@@ -11633,8 +11782,229 @@ var createWebGpuWorker = async () => {
|
|
|
11633
11782
|
};
|
|
11634
11783
|
|
|
11635
11784
|
// src/components/WebGpuElementsRenderer.tsx
|
|
11636
|
-
import { useEffect as
|
|
11637
|
-
import { SuperGrid
|
|
11785
|
+
import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
|
|
11786
|
+
import { SuperGrid, toMMSI } from "react-supergrid";
|
|
11787
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
11788
|
+
function WebGpuElementsRenderer(props) {
|
|
11789
|
+
const { elements, primitives, transform, width = 500, height = 500 } = props;
|
|
11790
|
+
const holder = useRef3(null);
|
|
11791
|
+
const workerRef = useRef3(void 0);
|
|
11792
|
+
const ready = useRef3(false);
|
|
11793
|
+
const [supportsXRayNet, setSupportsXRayNet] = useState4(null);
|
|
11794
|
+
const [failure, setFailure] = useState4();
|
|
11795
|
+
const selectedLayer = useGlobalStore((s) => s.selected_layer);
|
|
11796
|
+
const hiddenLayerOpacity = useGlobalStore((s) => s.hidden_layer_opacity);
|
|
11797
|
+
const showCopperPours = useGlobalStore((s) => s.is_showing_copper_pours);
|
|
11798
|
+
const showSolderMask = useGlobalStore((s) => s.is_showing_solder_mask);
|
|
11799
|
+
const showSilkscreen = useGlobalStore((s) => s.is_showing_silkscreen);
|
|
11800
|
+
const showFabricationNotes = useGlobalStore(
|
|
11801
|
+
(s) => s.is_showing_fabrication_notes
|
|
11802
|
+
);
|
|
11803
|
+
const showPcbNotes = useGlobalStore((s) => s.is_showing_pcb_notes);
|
|
11804
|
+
const showCourtyards = useGlobalStore((s) => s.is_showing_courtyards);
|
|
11805
|
+
const highlightedElementIds = useMemo3(
|
|
11806
|
+
() => [
|
|
11807
|
+
...new Set(
|
|
11808
|
+
primitives.flatMap((p) => {
|
|
11809
|
+
if (!p.is_mouse_over && !p.is_in_highlighted_net) return [];
|
|
11810
|
+
const element = p._element;
|
|
11811
|
+
const id = element?.[`${element.type}_id`];
|
|
11812
|
+
return id ? [id] : [];
|
|
11813
|
+
})
|
|
11814
|
+
)
|
|
11815
|
+
],
|
|
11816
|
+
[primitives]
|
|
11817
|
+
);
|
|
11818
|
+
const xRayElementIds = useMemo3(
|
|
11819
|
+
() => (props.xRayElements ?? []).map(
|
|
11820
|
+
(element) => element[`${element.type}_id`]
|
|
11821
|
+
),
|
|
11822
|
+
[props.xRayElements]
|
|
11823
|
+
);
|
|
11824
|
+
const options = useMemo3(
|
|
11825
|
+
() => ({
|
|
11826
|
+
selectedLayer,
|
|
11827
|
+
hiddenLayerOpacity,
|
|
11828
|
+
showCopperPours,
|
|
11829
|
+
showSolderMask,
|
|
11830
|
+
showSilkscreen,
|
|
11831
|
+
showFabricationNotes,
|
|
11832
|
+
showPcbNotes,
|
|
11833
|
+
showCourtyards,
|
|
11834
|
+
highlightedElementIds,
|
|
11835
|
+
xRayElementIds
|
|
11836
|
+
}),
|
|
11837
|
+
[
|
|
11838
|
+
selectedLayer,
|
|
11839
|
+
hiddenLayerOpacity,
|
|
11840
|
+
showCopperPours,
|
|
11841
|
+
showSolderMask,
|
|
11842
|
+
showSilkscreen,
|
|
11843
|
+
showFabricationNotes,
|
|
11844
|
+
showPcbNotes,
|
|
11845
|
+
showCourtyards,
|
|
11846
|
+
highlightedElementIds,
|
|
11847
|
+
xRayElementIds
|
|
11848
|
+
]
|
|
11849
|
+
);
|
|
11850
|
+
const sceneRef = useRef3(elements);
|
|
11851
|
+
const viewRef = useRef3(void 0);
|
|
11852
|
+
sceneRef.current = elements;
|
|
11853
|
+
if (transform) {
|
|
11854
|
+
const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
11855
|
+
viewRef.current = {
|
|
11856
|
+
type: "view",
|
|
11857
|
+
width: Math.max(1, Math.round(width * ratio)),
|
|
11858
|
+
height: Math.max(1, Math.round(height * ratio)),
|
|
11859
|
+
transform: {
|
|
11860
|
+
a: transform.a * ratio,
|
|
11861
|
+
b: transform.b * ratio,
|
|
11862
|
+
c: transform.c * ratio,
|
|
11863
|
+
d: transform.d * ratio,
|
|
11864
|
+
e: transform.e * ratio,
|
|
11865
|
+
f: transform.f * ratio
|
|
11866
|
+
},
|
|
11867
|
+
options
|
|
11868
|
+
};
|
|
11869
|
+
}
|
|
11870
|
+
useEffect5(() => {
|
|
11871
|
+
let disposed = false, failed = false, worker;
|
|
11872
|
+
const canvas = document.createElement("canvas");
|
|
11873
|
+
canvas.className = "pcb-webgpu-canvas";
|
|
11874
|
+
canvas.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none";
|
|
11875
|
+
holder.current?.append(canvas);
|
|
11876
|
+
const fail = (message) => {
|
|
11877
|
+
if (disposed || failed) return;
|
|
11878
|
+
failed = true;
|
|
11879
|
+
clearTimeout(timeout);
|
|
11880
|
+
canvas.remove();
|
|
11881
|
+
ready.current = false;
|
|
11882
|
+
worker?.terminate();
|
|
11883
|
+
worker = void 0;
|
|
11884
|
+
workerRef.current = void 0;
|
|
11885
|
+
if (holder.current) holder.current.dataset.xRayNetActive = "false";
|
|
11886
|
+
setFailure(message);
|
|
11887
|
+
};
|
|
11888
|
+
const timeout = setTimeout(
|
|
11889
|
+
() => fail("WebGPU worker startup timed out"),
|
|
11890
|
+
15e3
|
|
11891
|
+
);
|
|
11892
|
+
void (async () => {
|
|
11893
|
+
try {
|
|
11894
|
+
if (!navigator.gpu || typeof Worker === "undefined" || !canvas.transferControlToOffscreen)
|
|
11895
|
+
throw new Error("WebGPU workers are unavailable");
|
|
11896
|
+
worker = await createWebGpuWorker();
|
|
11897
|
+
if (disposed || failed) {
|
|
11898
|
+
worker.terminate();
|
|
11899
|
+
return;
|
|
11900
|
+
}
|
|
11901
|
+
workerRef.current = worker;
|
|
11902
|
+
worker.onmessage = ({ data }) => {
|
|
11903
|
+
if (disposed || failed) return;
|
|
11904
|
+
if (data.type === "rendered") {
|
|
11905
|
+
if (holder.current)
|
|
11906
|
+
holder.current.dataset.xRayNetActive = String(data.xRayActive);
|
|
11907
|
+
} else if (data.type === "error") {
|
|
11908
|
+
fail(data.message);
|
|
11909
|
+
} else if (data.type === "ready") {
|
|
11910
|
+
clearTimeout(timeout);
|
|
11911
|
+
setSupportsXRayNet(data.supportsXRayNet);
|
|
11912
|
+
ready.current = true;
|
|
11913
|
+
worker.postMessage({
|
|
11914
|
+
type: "scene",
|
|
11915
|
+
elements: sceneRef.current
|
|
11916
|
+
});
|
|
11917
|
+
if (viewRef.current) worker.postMessage(viewRef.current);
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
worker.onerror = (event) => {
|
|
11921
|
+
event.preventDefault();
|
|
11922
|
+
fail(event.message || "WebGPU worker failed");
|
|
11923
|
+
};
|
|
11924
|
+
worker.addEventListener("messageerror", () => {
|
|
11925
|
+
fail("WebGPU worker communication failed");
|
|
11926
|
+
});
|
|
11927
|
+
const offscreen = canvas.transferControlToOffscreen();
|
|
11928
|
+
worker.postMessage(
|
|
11929
|
+
{ type: "init", canvas: offscreen },
|
|
11930
|
+
[offscreen]
|
|
11931
|
+
);
|
|
11932
|
+
} catch (error) {
|
|
11933
|
+
fail(String(error));
|
|
11934
|
+
}
|
|
11935
|
+
})();
|
|
11936
|
+
return () => {
|
|
11937
|
+
disposed = true;
|
|
11938
|
+
clearTimeout(timeout);
|
|
11939
|
+
ready.current = false;
|
|
11940
|
+
workerRef.current = void 0;
|
|
11941
|
+
worker?.terminate();
|
|
11942
|
+
canvas.remove();
|
|
11943
|
+
};
|
|
11944
|
+
}, []);
|
|
11945
|
+
useEffect5(() => {
|
|
11946
|
+
if (ready.current)
|
|
11947
|
+
workerRef.current?.postMessage({
|
|
11948
|
+
type: "scene",
|
|
11949
|
+
elements
|
|
11950
|
+
});
|
|
11951
|
+
}, [elements]);
|
|
11952
|
+
useEffect5(() => {
|
|
11953
|
+
if (ready.current && viewRef.current)
|
|
11954
|
+
workerRef.current?.postMessage(viewRef.current);
|
|
11955
|
+
}, [transform, width, height, options]);
|
|
11956
|
+
const xRayUnavailable = Boolean(
|
|
11957
|
+
props.xRayElements && supportsXRayNet === false
|
|
11958
|
+
);
|
|
11959
|
+
return /* @__PURE__ */ jsx3(
|
|
11960
|
+
"div",
|
|
11961
|
+
{
|
|
11962
|
+
ref: holder,
|
|
11963
|
+
"data-pcb-renderer": "webgpu",
|
|
11964
|
+
"data-webgpu-error": failure,
|
|
11965
|
+
style: {
|
|
11966
|
+
width,
|
|
11967
|
+
height,
|
|
11968
|
+
background: "black",
|
|
11969
|
+
position: "relative",
|
|
11970
|
+
overflow: "hidden"
|
|
11971
|
+
},
|
|
11972
|
+
children: failure ? /* @__PURE__ */ jsxs(
|
|
11973
|
+
"div",
|
|
11974
|
+
{
|
|
11975
|
+
role: "alert",
|
|
11976
|
+
style: { padding: 16, color: "white", position: "relative" },
|
|
11977
|
+
children: [
|
|
11978
|
+
/* @__PURE__ */ jsx3("strong", { children: "WebGPU rendering failed" }),
|
|
11979
|
+
/* @__PURE__ */ jsx3("div", { children: failure })
|
|
11980
|
+
]
|
|
11981
|
+
}
|
|
11982
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11983
|
+
/* @__PURE__ */ jsx3(
|
|
11984
|
+
SuperGrid,
|
|
11985
|
+
{
|
|
11986
|
+
textColor: "rgba(0,255,0,0.8)",
|
|
11987
|
+
majorColor: "rgba(0,255,0,0.4)",
|
|
11988
|
+
minorColor: "rgba(0,255,0,0.2)",
|
|
11989
|
+
screenSpaceCellSize: 200,
|
|
11990
|
+
width,
|
|
11991
|
+
height,
|
|
11992
|
+
transform,
|
|
11993
|
+
stringifyCoord: (x, y, z) => `${toMMSI(x, z)}, ${toMMSI(y, z)}`
|
|
11994
|
+
}
|
|
11995
|
+
),
|
|
11996
|
+
xRayUnavailable && /* @__PURE__ */ jsx3(
|
|
11997
|
+
"div",
|
|
11998
|
+
{
|
|
11999
|
+
role: "status",
|
|
12000
|
+
style: { padding: 16, color: "white", position: "relative" },
|
|
12001
|
+
children: "X-Ray is unavailable in this WebGPU renderer."
|
|
12002
|
+
}
|
|
12003
|
+
)
|
|
12004
|
+
] })
|
|
12005
|
+
}
|
|
12006
|
+
);
|
|
12007
|
+
}
|
|
11638
12008
|
|
|
11639
12009
|
// src/components/CanvasPrimitiveRenderer.tsx
|
|
11640
12010
|
import { CircuitToCanvasDrawer as CircuitToCanvasDrawer17 } from "circuit-to-canvas";
|
|
@@ -13577,9 +13947,9 @@ var getPrimitivesForDrawer = ({
|
|
|
13577
13947
|
).filter((primitive) => primitive._element?.type !== "pcb_via").filter((primitive) => primitive._element?.type !== "pcb_trace").filter((primitive) => primitive._element?.type !== "pcb_copper_text");
|
|
13578
13948
|
|
|
13579
13949
|
// src/components/CanvasPrimitiveRenderer.tsx
|
|
13580
|
-
import { useEffect as
|
|
13581
|
-
import { SuperGrid, toMMSI } from "react-supergrid";
|
|
13582
|
-
import { jsx as
|
|
13950
|
+
import { useEffect as useEffect6, useRef as useRef4 } from "react";
|
|
13951
|
+
import { SuperGrid as SuperGrid2, toMMSI as toMMSI2 } from "react-supergrid";
|
|
13952
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
13583
13953
|
var CanvasPrimitiveRenderer = ({
|
|
13584
13954
|
primitives,
|
|
13585
13955
|
elements,
|
|
@@ -13589,8 +13959,8 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13589
13959
|
width = 500,
|
|
13590
13960
|
height = 500
|
|
13591
13961
|
}) => {
|
|
13592
|
-
const xRayCanvasRef =
|
|
13593
|
-
const canvasRefs =
|
|
13962
|
+
const xRayCanvasRef = useRef4(null);
|
|
13963
|
+
const canvasRefs = useRef4({});
|
|
13594
13964
|
const hiddenLayerOpacity = useGlobalStore((s) => s.hidden_layer_opacity);
|
|
13595
13965
|
const selectedLayer = useGlobalStore((s) => s.selected_layer);
|
|
13596
13966
|
const isShowingCopperPours = useGlobalStore((s) => s.is_showing_copper_pours);
|
|
@@ -13601,7 +13971,7 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13601
13971
|
const isShowingPcbNotes = useGlobalStore((s) => s.is_showing_pcb_notes);
|
|
13602
13972
|
const isShowingCourtyards = useGlobalStore((s) => s.is_showing_courtyards);
|
|
13603
13973
|
const isShowingSilkscreen = useGlobalStore((s) => s.is_showing_silkscreen);
|
|
13604
|
-
|
|
13974
|
+
useEffect6(() => {
|
|
13605
13975
|
if (!canvasRefs.current) return;
|
|
13606
13976
|
if (Object.keys(canvasRefs.current).length === 0) return;
|
|
13607
13977
|
const availableCanvasRefs = Object.fromEntries(
|
|
@@ -13898,7 +14268,7 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13898
14268
|
isShowingCourtyards,
|
|
13899
14269
|
isShowingSilkscreen
|
|
13900
14270
|
]);
|
|
13901
|
-
return /* @__PURE__ */
|
|
14271
|
+
return /* @__PURE__ */ jsxs2(
|
|
13902
14272
|
"div",
|
|
13903
14273
|
{
|
|
13904
14274
|
style: {
|
|
@@ -13908,8 +14278,8 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13908
14278
|
position: "relative"
|
|
13909
14279
|
},
|
|
13910
14280
|
children: [
|
|
13911
|
-
/* @__PURE__ */
|
|
13912
|
-
|
|
14281
|
+
/* @__PURE__ */ jsx4(
|
|
14282
|
+
SuperGrid2,
|
|
13913
14283
|
{
|
|
13914
14284
|
textColor: "rgba(0,255,0,0.8)",
|
|
13915
14285
|
majorColor: "rgba(0,255,0,0.4)",
|
|
@@ -13918,10 +14288,10 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13918
14288
|
width,
|
|
13919
14289
|
height,
|
|
13920
14290
|
transform,
|
|
13921
|
-
stringifyCoord: (x, y, z) => `${
|
|
14291
|
+
stringifyCoord: (x, y, z) => `${toMMSI2(x, z)}, ${toMMSI2(y, z)}`
|
|
13922
14292
|
}
|
|
13923
14293
|
),
|
|
13924
|
-
xRayElements && /* @__PURE__ */
|
|
14294
|
+
xRayElements && /* @__PURE__ */ jsx4(
|
|
13925
14295
|
"canvas",
|
|
13926
14296
|
{
|
|
13927
14297
|
ref: xRayCanvasRef,
|
|
@@ -13941,7 +14311,7 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13941
14311
|
if (!isShowingSolderMask && layer.includes("soldermask")) return false;
|
|
13942
14312
|
if (!isShowingSilkscreen && layer.includes("silkscreen")) return false;
|
|
13943
14313
|
return true;
|
|
13944
|
-
}).map((l) => l.replace(/-/g, "")).map((layer, i) => /* @__PURE__ */
|
|
14314
|
+
}).map((l) => l.replace(/-/g, "")).map((layer, i) => /* @__PURE__ */ jsx4(
|
|
13945
14315
|
"canvas",
|
|
13946
14316
|
{
|
|
13947
14317
|
className: `pcb-layer-${layer}`,
|
|
@@ -13970,217 +14340,6 @@ var CanvasPrimitiveRenderer = ({
|
|
|
13970
14340
|
);
|
|
13971
14341
|
};
|
|
13972
14342
|
|
|
13973
|
-
// src/components/WebGpuElementsRenderer.tsx
|
|
13974
|
-
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
13975
|
-
function WebGpuElementsRenderer(props) {
|
|
13976
|
-
const { elements, primitives, transform, width = 500, height = 500 } = props;
|
|
13977
|
-
const holder = useRef4(null);
|
|
13978
|
-
const workerRef = useRef4(void 0);
|
|
13979
|
-
const ready = useRef4(false);
|
|
13980
|
-
const [supportsXRayNet, setSupportsXRayNet] = useState4(false);
|
|
13981
|
-
const [failure, setFailure] = useState4();
|
|
13982
|
-
const selectedLayer = useGlobalStore((s) => s.selected_layer);
|
|
13983
|
-
const hiddenLayerOpacity = useGlobalStore((s) => s.hidden_layer_opacity);
|
|
13984
|
-
const showCopperPours = useGlobalStore((s) => s.is_showing_copper_pours);
|
|
13985
|
-
const showSolderMask = useGlobalStore((s) => s.is_showing_solder_mask);
|
|
13986
|
-
const showSilkscreen = useGlobalStore((s) => s.is_showing_silkscreen);
|
|
13987
|
-
const showFabricationNotes = useGlobalStore(
|
|
13988
|
-
(s) => s.is_showing_fabrication_notes
|
|
13989
|
-
);
|
|
13990
|
-
const showPcbNotes = useGlobalStore((s) => s.is_showing_pcb_notes);
|
|
13991
|
-
const showCourtyards = useGlobalStore((s) => s.is_showing_courtyards);
|
|
13992
|
-
const highlightedElementIds = useMemo3(
|
|
13993
|
-
() => [
|
|
13994
|
-
...new Set(
|
|
13995
|
-
primitives.flatMap((p) => {
|
|
13996
|
-
if (!p.is_mouse_over && !p.is_in_highlighted_net) return [];
|
|
13997
|
-
const element = p._element;
|
|
13998
|
-
const id = element?.[`${element.type}_id`];
|
|
13999
|
-
return id ? [id] : [];
|
|
14000
|
-
})
|
|
14001
|
-
)
|
|
14002
|
-
],
|
|
14003
|
-
[primitives]
|
|
14004
|
-
);
|
|
14005
|
-
const xRayElementIds = useMemo3(
|
|
14006
|
-
() => (props.xRayElements ?? []).map(
|
|
14007
|
-
(element) => element[`${element.type}_id`]
|
|
14008
|
-
),
|
|
14009
|
-
[props.xRayElements]
|
|
14010
|
-
);
|
|
14011
|
-
const options = useMemo3(
|
|
14012
|
-
() => ({
|
|
14013
|
-
selectedLayer,
|
|
14014
|
-
hiddenLayerOpacity,
|
|
14015
|
-
showCopperPours,
|
|
14016
|
-
showSolderMask,
|
|
14017
|
-
showSilkscreen,
|
|
14018
|
-
showFabricationNotes,
|
|
14019
|
-
showPcbNotes,
|
|
14020
|
-
showCourtyards,
|
|
14021
|
-
highlightedElementIds,
|
|
14022
|
-
xRayElementIds
|
|
14023
|
-
}),
|
|
14024
|
-
[
|
|
14025
|
-
selectedLayer,
|
|
14026
|
-
hiddenLayerOpacity,
|
|
14027
|
-
showCopperPours,
|
|
14028
|
-
showSolderMask,
|
|
14029
|
-
showSilkscreen,
|
|
14030
|
-
showFabricationNotes,
|
|
14031
|
-
showPcbNotes,
|
|
14032
|
-
showCourtyards,
|
|
14033
|
-
highlightedElementIds,
|
|
14034
|
-
xRayElementIds
|
|
14035
|
-
]
|
|
14036
|
-
);
|
|
14037
|
-
const sceneRef = useRef4(elements);
|
|
14038
|
-
const viewRef = useRef4(void 0);
|
|
14039
|
-
sceneRef.current = elements;
|
|
14040
|
-
if (transform) {
|
|
14041
|
-
const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
14042
|
-
viewRef.current = {
|
|
14043
|
-
type: "view",
|
|
14044
|
-
width: Math.max(1, Math.round(width * ratio)),
|
|
14045
|
-
height: Math.max(1, Math.round(height * ratio)),
|
|
14046
|
-
transform: {
|
|
14047
|
-
a: transform.a * ratio,
|
|
14048
|
-
b: transform.b * ratio,
|
|
14049
|
-
c: transform.c * ratio,
|
|
14050
|
-
d: transform.d * ratio,
|
|
14051
|
-
e: transform.e * ratio,
|
|
14052
|
-
f: transform.f * ratio
|
|
14053
|
-
},
|
|
14054
|
-
options
|
|
14055
|
-
};
|
|
14056
|
-
}
|
|
14057
|
-
useEffect6(() => {
|
|
14058
|
-
let disposed = false, failed = false, worker;
|
|
14059
|
-
const canvas = document.createElement("canvas");
|
|
14060
|
-
canvas.className = "pcb-webgpu-canvas";
|
|
14061
|
-
canvas.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none";
|
|
14062
|
-
holder.current?.append(canvas);
|
|
14063
|
-
const fallback = (message) => {
|
|
14064
|
-
if (disposed || failed) return;
|
|
14065
|
-
failed = true;
|
|
14066
|
-
canvas.remove();
|
|
14067
|
-
ready.current = false;
|
|
14068
|
-
worker?.terminate();
|
|
14069
|
-
workerRef.current = void 0;
|
|
14070
|
-
setFailure(message);
|
|
14071
|
-
};
|
|
14072
|
-
const timeout = setTimeout(
|
|
14073
|
-
() => fallback("WebGPU worker startup timed out"),
|
|
14074
|
-
15e3
|
|
14075
|
-
);
|
|
14076
|
-
void (async () => {
|
|
14077
|
-
try {
|
|
14078
|
-
if (!navigator.gpu || typeof Worker === "undefined" || !canvas.transferControlToOffscreen)
|
|
14079
|
-
throw new Error("WebGPU workers are unavailable");
|
|
14080
|
-
worker = await createWebGpuWorker();
|
|
14081
|
-
if (disposed || failed) {
|
|
14082
|
-
worker.terminate();
|
|
14083
|
-
return;
|
|
14084
|
-
}
|
|
14085
|
-
workerRef.current = worker;
|
|
14086
|
-
worker.onmessage = ({ data }) => {
|
|
14087
|
-
if (disposed || failed) return;
|
|
14088
|
-
if (data.type === "rendered") {
|
|
14089
|
-
if (holder.current)
|
|
14090
|
-
holder.current.dataset.xRayNetActive = String(data.xRayActive);
|
|
14091
|
-
} else if (data.type === "error") {
|
|
14092
|
-
clearTimeout(timeout);
|
|
14093
|
-
fallback(data.message);
|
|
14094
|
-
} else if (data.type === "ready") {
|
|
14095
|
-
clearTimeout(timeout);
|
|
14096
|
-
setSupportsXRayNet(data.supportsXRayNet);
|
|
14097
|
-
ready.current = true;
|
|
14098
|
-
worker.postMessage({
|
|
14099
|
-
type: "scene",
|
|
14100
|
-
elements: sceneRef.current
|
|
14101
|
-
});
|
|
14102
|
-
if (viewRef.current) worker.postMessage(viewRef.current);
|
|
14103
|
-
}
|
|
14104
|
-
};
|
|
14105
|
-
worker.onerror = (event) => {
|
|
14106
|
-
event.preventDefault();
|
|
14107
|
-
clearTimeout(timeout);
|
|
14108
|
-
fallback(event.message || "WebGPU worker failed");
|
|
14109
|
-
};
|
|
14110
|
-
worker.onmessageerror = () => {
|
|
14111
|
-
clearTimeout(timeout);
|
|
14112
|
-
fallback("WebGPU worker communication failed");
|
|
14113
|
-
};
|
|
14114
|
-
const offscreen = canvas.transferControlToOffscreen();
|
|
14115
|
-
worker.postMessage(
|
|
14116
|
-
{ type: "init", canvas: offscreen },
|
|
14117
|
-
[offscreen]
|
|
14118
|
-
);
|
|
14119
|
-
} catch (error) {
|
|
14120
|
-
clearTimeout(timeout);
|
|
14121
|
-
fallback(String(error));
|
|
14122
|
-
}
|
|
14123
|
-
})();
|
|
14124
|
-
return () => {
|
|
14125
|
-
disposed = true;
|
|
14126
|
-
clearTimeout(timeout);
|
|
14127
|
-
ready.current = false;
|
|
14128
|
-
workerRef.current = void 0;
|
|
14129
|
-
worker?.terminate();
|
|
14130
|
-
canvas.remove();
|
|
14131
|
-
};
|
|
14132
|
-
}, []);
|
|
14133
|
-
useEffect6(() => {
|
|
14134
|
-
if (ready.current)
|
|
14135
|
-
workerRef.current?.postMessage({
|
|
14136
|
-
type: "scene",
|
|
14137
|
-
elements
|
|
14138
|
-
});
|
|
14139
|
-
}, [elements]);
|
|
14140
|
-
useEffect6(() => {
|
|
14141
|
-
if (ready.current && viewRef.current)
|
|
14142
|
-
workerRef.current?.postMessage(viewRef.current);
|
|
14143
|
-
}, [transform, width, height, options]);
|
|
14144
|
-
const fallbackElements = useMemo3(
|
|
14145
|
-
() => showCopperPours ? elements : elements.filter((e) => e.type !== "pcb_copper_pour"),
|
|
14146
|
-
[elements, showCopperPours]
|
|
14147
|
-
);
|
|
14148
|
-
if (failure)
|
|
14149
|
-
return /* @__PURE__ */ jsx4("div", { "data-pcb-renderer": "canvas", "data-webgpu-fallback": failure, children: /* @__PURE__ */ jsx4(CanvasPrimitiveRenderer, { ...props, elements: fallbackElements }) });
|
|
14150
|
-
const useXRayFallback = Boolean(props.xRayElements && !supportsXRayNet);
|
|
14151
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
14152
|
-
useXRayFallback && /* @__PURE__ */ jsx4("div", { "data-x-ray-fallback": "unsupported-webgpu-version", children: /* @__PURE__ */ jsx4(CanvasPrimitiveRenderer, { ...props, elements: fallbackElements }) }),
|
|
14153
|
-
/* @__PURE__ */ jsx4(
|
|
14154
|
-
"div",
|
|
14155
|
-
{
|
|
14156
|
-
ref: holder,
|
|
14157
|
-
"data-pcb-renderer": "webgpu",
|
|
14158
|
-
style: {
|
|
14159
|
-
width,
|
|
14160
|
-
height,
|
|
14161
|
-
background: "black",
|
|
14162
|
-
position: "relative",
|
|
14163
|
-
overflow: "hidden",
|
|
14164
|
-
display: useXRayFallback ? "none" : void 0
|
|
14165
|
-
},
|
|
14166
|
-
children: /* @__PURE__ */ jsx4(
|
|
14167
|
-
SuperGrid2,
|
|
14168
|
-
{
|
|
14169
|
-
textColor: "rgba(0,255,0,0.8)",
|
|
14170
|
-
majorColor: "rgba(0,255,0,0.4)",
|
|
14171
|
-
minorColor: "rgba(0,255,0,0.2)",
|
|
14172
|
-
screenSpaceCellSize: 200,
|
|
14173
|
-
width,
|
|
14174
|
-
height,
|
|
14175
|
-
transform,
|
|
14176
|
-
stringifyCoord: (x, y, z) => `${toMMSI2(x, z)}, ${toMMSI2(y, z)}`
|
|
14177
|
-
}
|
|
14178
|
-
)
|
|
14179
|
-
}
|
|
14180
|
-
)
|
|
14181
|
-
] });
|
|
14182
|
-
}
|
|
14183
|
-
|
|
14184
14343
|
// node_modules/graphics-debug/dist/chunk-OHVOYMJ4.js
|
|
14185
14344
|
import {
|
|
14186
14345
|
glyphAdvanceRatio,
|
|
@@ -18359,7 +18518,7 @@ import { css as css3 } from "@emotion/css";
|
|
|
18359
18518
|
// package.json
|
|
18360
18519
|
var package_default = {
|
|
18361
18520
|
name: "@tscircuit/pcb-viewer",
|
|
18362
|
-
version: "1.11.
|
|
18521
|
+
version: "1.11.413",
|
|
18363
18522
|
main: "dist/index.js",
|
|
18364
18523
|
type: "module",
|
|
18365
18524
|
repository: {
|
|
@@ -18391,6 +18550,7 @@ var package_default = {
|
|
|
18391
18550
|
"@swc/core": "^1.4.12",
|
|
18392
18551
|
"@tscircuit/circuit-json-util": "^0.0.116",
|
|
18393
18552
|
"@tscircuit/circuit-json-webgpu": "github:tscircuit/circuit-json-webgpu#6c1414c7464a743190747c99bb8ec504f2ecf646",
|
|
18553
|
+
"@tscircuit/core": "^0.0.1701",
|
|
18394
18554
|
"@tscircuit/eagle-xml-converter": "^1.0.0",
|
|
18395
18555
|
"@types/bun": "latest",
|
|
18396
18556
|
"@types/color": "^3.0.6",
|
|
@@ -18417,8 +18577,7 @@ var package_default = {
|
|
|
18417
18577
|
},
|
|
18418
18578
|
peerDependencies: {
|
|
18419
18579
|
react: "*",
|
|
18420
|
-
"react-dom": "*"
|
|
18421
|
-
tscircuit: "*"
|
|
18580
|
+
"react-dom": "*"
|
|
18422
18581
|
},
|
|
18423
18582
|
dependencies: {
|
|
18424
18583
|
"@emotion/css": "^11.11.2",
|
|
@@ -20637,9 +20796,65 @@ var calculateBoardSizeKey = (circuitJson) => {
|
|
|
20637
20796
|
return `${round(board.width)}_${round(board.height)}`;
|
|
20638
20797
|
};
|
|
20639
20798
|
|
|
20799
|
+
// src/lib/apply-edit-events.ts
|
|
20800
|
+
import { translate as translate13 } from "transformation-matrix";
|
|
20801
|
+
function applyEditEvents({
|
|
20802
|
+
circuitJson,
|
|
20803
|
+
editEvents
|
|
20804
|
+
}) {
|
|
20805
|
+
let editedCircuitJson = structuredClone(circuitJson);
|
|
20806
|
+
for (const editEvent of editEvents) {
|
|
20807
|
+
if (editEvent.edit_event_type === "edit_pcb_component_location") {
|
|
20808
|
+
const pcbComponent = su(editedCircuitJson).pcb_component.get(
|
|
20809
|
+
editEvent.pcb_component_id
|
|
20810
|
+
);
|
|
20811
|
+
const isAlreadyAtEditedCenter = pcbComponent?.center.x === editEvent.new_center.x && pcbComponent.center.y === editEvent.new_center.y;
|
|
20812
|
+
if (isAlreadyAtEditedCenter) continue;
|
|
20813
|
+
const pcbToEditedPcbTransform = translate13(
|
|
20814
|
+
editEvent.new_center.x - editEvent.original_center.x,
|
|
20815
|
+
editEvent.new_center.y - editEvent.original_center.y
|
|
20816
|
+
);
|
|
20817
|
+
editedCircuitJson = editedCircuitJson.map(
|
|
20818
|
+
(element) => "pcb_component_id" in element && element.pcb_component_id === editEvent.pcb_component_id ? transformPCBElement(element, pcbToEditedPcbTransform) : element
|
|
20819
|
+
);
|
|
20820
|
+
continue;
|
|
20821
|
+
}
|
|
20822
|
+
if (editEvent.edit_event_type === "edit_schematic_component_location") {
|
|
20823
|
+
editedCircuitJson = editedCircuitJson.map(
|
|
20824
|
+
(element) => element.type === "schematic_component" && element.schematic_component_id === editEvent.schematic_component_id ? { ...element, center: editEvent.new_center } : element
|
|
20825
|
+
);
|
|
20826
|
+
continue;
|
|
20827
|
+
}
|
|
20828
|
+
if (editEvent.edit_event_type !== "edit_pcb_trace_hint") continue;
|
|
20829
|
+
if (!editEvent.pcb_trace_hint_id) continue;
|
|
20830
|
+
const pcbTraceHint = su(editedCircuitJson).pcb_trace_hint.get(
|
|
20831
|
+
editEvent.pcb_trace_hint_id
|
|
20832
|
+
);
|
|
20833
|
+
if (pcbTraceHint) {
|
|
20834
|
+
editedCircuitJson = editedCircuitJson.map(
|
|
20835
|
+
(element) => element.type === "pcb_trace_hint" && element.pcb_trace_hint_id === editEvent.pcb_trace_hint_id ? { ...element, route: editEvent.route } : element
|
|
20836
|
+
);
|
|
20837
|
+
continue;
|
|
20838
|
+
}
|
|
20839
|
+
const pcbPort = su(editedCircuitJson).pcb_port.get(editEvent.pcb_port_id);
|
|
20840
|
+
if (!pcbPort?.pcb_component_id) continue;
|
|
20841
|
+
const newPcbTraceHint = {
|
|
20842
|
+
type: "pcb_trace_hint",
|
|
20843
|
+
pcb_trace_hint_id: editEvent.pcb_trace_hint_id,
|
|
20844
|
+
route: editEvent.route,
|
|
20845
|
+
pcb_port_id: editEvent.pcb_port_id,
|
|
20846
|
+
pcb_component_id: pcbPort.pcb_component_id
|
|
20847
|
+
};
|
|
20848
|
+
editedCircuitJson = editedCircuitJson.filter(
|
|
20849
|
+
(element) => element.type !== "pcb_trace_hint" || element.pcb_port_id !== editEvent.pcb_port_id
|
|
20850
|
+
).concat(newPcbTraceHint);
|
|
20851
|
+
}
|
|
20852
|
+
return editedCircuitJson;
|
|
20853
|
+
}
|
|
20854
|
+
|
|
20640
20855
|
// src/PCBViewer.tsx
|
|
20641
20856
|
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
20642
|
-
var defaultTransform = compose9(
|
|
20857
|
+
var defaultTransform = compose9(translate14(400, 300), scale6(40, -40));
|
|
20643
20858
|
var PCBViewer = ({
|
|
20644
20859
|
circuitJson,
|
|
20645
20860
|
debugGraphics,
|
|
@@ -20719,9 +20934,9 @@ var PCBViewer = ({
|
|
|
20719
20934
|
100
|
|
20720
20935
|
) * 0.75;
|
|
20721
20936
|
const targetTransform = compose9(
|
|
20722
|
-
|
|
20937
|
+
translate14((elmBounds.width ?? 0) / 2, (elmBounds.height ?? 0) / 2),
|
|
20723
20938
|
scale6(scaleFactor, -scaleFactor, 0, 0),
|
|
20724
|
-
|
|
20939
|
+
translate14(-center.x, -center.y)
|
|
20725
20940
|
);
|
|
20726
20941
|
setTransform(targetTransform);
|
|
20727
20942
|
return;
|