This commit is contained in:
李志鹏
2026-01-14 14:43:43 +08:00
parent e42975159f
commit 810dd2351b
6 changed files with 1008 additions and 27 deletions

View File

@@ -94,6 +94,10 @@
type: Object,
required: true,
},
partManager: {
type: Object,
required: true,
},
layerManager: {
type: Object,
required: true,

View File

@@ -166,19 +166,19 @@ const normalToolsList = ref([
icon: { name: "CFont", size: "20" },
class: "text-btn",
},
// {
// id: OperationType.PART,
// title: t("Canvas.GarmentPartSelector"),
// action: () => selectTool(OperationType.PART),
// icon: { name: "CPart", size: "28" },
// class: "part-btn",
// activeList: [
// OperationType.PART,
// OperationType.PART_RECTANGLE,
// OperationType.PART_BRUSH,
// OperationType.PART_ERASER,
// ],
// },
{
id: OperationType.PART,
title: t("Canvas.GarmentPartSelector"),
action: () => selectTool(OperationType.PART),
icon: { name: "CPart", size: "28" },
class: "part-btn",
activeList: [
OperationType.PART,
OperationType.PART_RECTANGLE,
OperationType.PART_BRUSH,
OperationType.PART_ERASER,
],
},
{
id: "help",
title: t("Canvas.help"),

View File

@@ -17,6 +17,7 @@ import { KeyboardManager } from "./managers/events/KeyboardManager.js";
import CanvasConfig from "./config/canvasConfig.js";
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
import { SelectionManager } from "./managers/selection/SelectionManager";
import { PartManager } from "./managers/PartManager";
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
import texturePresetManager from "./managers/brushes/TexturePresetManager";
import { BrushStore } from "./store/BrushStore";
@@ -185,6 +186,7 @@ let keyboardManager = null;
let toolManager = null;
let liquifyManager = null;
let selectionManager = null;
let partManager = null;
let redGreenModeManager = null;
// 快捷键帮助模态框状态
@@ -226,6 +228,7 @@ function handleCanvasInit(isLoadJson = false) {
keyboardManager,
liquifyManager,
selectionManager,
partManager,
redGreenModeManager,
});
}
@@ -378,6 +381,13 @@ onMounted(async () => {
});
canvasManager.setSelectionManager(selectionManager);
// 初始化部件选择管理器
partManager = new PartManager({
canvas: canvasManager.canvas,
layerManager,
});
canvasManager.setPartManager(partManager);
if (props.canvasJSON) {
// 如果传入了初始JSON数据加载到画布上
if (typeof props.canvasJSON === "string") {
@@ -537,6 +547,7 @@ onBeforeUnmount(async () => {
toolManager?.dispose?.();
liquifyManager?.dispose?.();
selectionManager?.dispose?.();
partManager?.dispose?.();
redGreenModeManager?.dispose?.();
// minimapManager?.dispose?.();
canvasManager = null;
@@ -546,6 +557,7 @@ onBeforeUnmount(async () => {
toolManager = null;
liquifyManager = null;
selectionManager = null;
partManager = null;
redGreenModeManager = null;
// fabric.Object.prototype.controls.deleteControl = undefined;
@@ -1254,16 +1266,17 @@ defineExpose({
/>
<!-- 部件选取面板 -->
<!-- <PartSelectorPanel
<PartSelectorPanel
v-if="canvasManagerLoaded && !enabledRedGreenMode"
:canvas="canvasManager && canvasManager.canvas"
:commandManager="commandManager"
:selectionManager="selectionManager"
:partManager="partManager"
:layerManager="layerManager"
:canvasManager="canvasManager"
:toolManager="toolManager"
:activeTool="activeTool"
/> -->
/>
<!-- 文本编辑面板 -->
<TextEditorPanel

View File

@@ -304,6 +304,19 @@ export class CanvasManager {
}
}
/**
* 设置部件选择管理器
* @param {Object} partManager 部件选择管理器实例
*/
setPartManager(partManager) {
this.partManager = partManager;
// 如果已创建事件管理器,更新它的部件选择管理器引用
if (this.eventManager) {
this.eventManager.partManager = this.partManager;
}
}
// 设置红绿图模式管理器
setRedGreenModeManager(redGreenModeManager) {
this.redGreenModeManager = redGreenModeManager;

View File

@@ -0,0 +1,941 @@
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper";
import { OperationType } from "../utils/layerHelper";
import { CreateSelectionCommand } from "../commands/SelectionCommands";
import { ClearSelectionCommand } from "../commands/LassoCutoutCommand";
/**
* 部件选择管理器
*/
export class PartManager {
/**
* 创建部件选择管理器
* @param {Object} options 配置选项
* @param {Object} options.canvas fabric.js画布实例
* @param {Object} options.commandManager 命令管理器实例
* @param {Object} options.layerManager 图层管理实例
*/
constructor(options = {}) {
this.canvas = options.canvas;
this.commandManager = options.commandManager;
this.layerManager = options.layerManager;
// 选区状态
this.isActive = false;
this.selectionType = OperationType.LASSO_RECTANGLE; // 使用常量而不是字符串
this.selectionObject = null; // 当前选区对象
this.selectionId = "selection_" + Date.now();
this.featherAmount = 0; // 羽化值
// 选区样式配置
this.selectionStyle = {
stroke: "#0096ff",
strokeWidth: 1,
strokeDashArray: [5, 5],
fill: "rgba(0, 150, 255, 0.1)",
selectable: false,
evented: false,
excludeFromExport: true,
hoverCursor: "default",
moveCursor: "default",
};
// 绘制状态
this.drawingObject = null;
this.startPoint = null;
this.selectionPath = null; // 存储选区路径数据
// 自由选区相关状态
this.drawingPoints = null;
this.currentPathString = null;
// 不再直接绑定事件处理函数
this._mouseDownHandler = null;
this._mouseMoveHandler = null;
this._mouseUpHandler = null;
this._keyDownHandler = null;
// 选区相关的工具类型
this.tools = [
OperationType.PART,
OperationType.PART_RECTANGLE,
OperationType.PART_BRUSH,
OperationType.PART_ERASER,
];
// 当前工具
this.currentTool = OperationType.SELECT;
// 选区状态变化回调
this.onSelectionChanged = null;
// 不再自动初始化事件,改为手动控制
// this.initEvents();
}
/**
* 设置当前工具
* @param {String} toolId 工具ID
*/
setCurrentTool(toolId) {
this.currentTool = toolId;
// 检查是否为选区工具
const wasActive = this.isActive;
this.isActive = this.tools.includes(toolId);
// 如果从非选区工具切换到选区工具,初始化事件
if (!wasActive && this.isActive) {
this.initEvents();
}
// 如果从选区工具切换到非选区工具,清理事件和选区
else if (wasActive && !this.isActive) {
this.cleanupEvents();
this.clearSelection();
}
// 根据工具类型设置选区类型
if (this.isActive) {
this.selectionType = toolId;
}
}
/**
* 初始化选区相关事件
*/
initEvents() {
if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化
// 保存实例引用,用于事件处理函数中
const self = this;
// 鼠标按下事件处理
this._mouseDownHandler = (options) => {
// 如果选区功能未激活,不处理事件
if (!this.isActive) return;
// 如果点击的是已有对象且不是选区对象,则不处理
if (
options.target &&
options.target.id !== this.selectionId &&
options.target.selectable !== false &&
options.target.type !== "selection"
) {
return;
}
// 阻止事件冒泡,避免与 CanvasEventManager 冲突
options.e.stopPropagation();
// 根据选区类型执行不同的起始操作
switch (this.selectionType) {
case OperationType.LASSO:
this.startFreeSelection(options);
break;
case OperationType.LASSO_ELLIPSE:
this.startEllipseSelection(options);
break;
case OperationType.LASSO_RECTANGLE:
this.startRectangleSelection(options);
break;
}
};
// 鼠标移动事件处理
this._mouseMoveHandler = (options) => {
// 如果选区功能未激活或没有正在绘制的对象,不处理事件
if (!this.isActive || !this.drawingObject) return;
// 阻止事件冒泡
options.e.stopPropagation();
// 根据选区类型执行不同的绘制操作
switch (this.selectionType) {
case OperationType.LASSO_RECTANGLE:
this.drawRectangleSelection(options);
break;
case OperationType.LASSO_ELLIPSE:
this.drawEllipseSelection(options);
break;
case OperationType.LASSO:
this.drawFreeSelection(options);
break;
}
};
// 鼠标抬起事件处理
this._mouseUpHandler = (options) => {
// 如果选区功能未激活或没有正在绘制的对象,不处理事件
if (!this.isActive || !this.drawingObject) return;
// 阻止事件冒泡
if (options && options.e) {
options.e.stopPropagation();
}
// 根据选区类型执行不同的完成操作
switch (this.selectionType) {
case OperationType.LASSO_RECTANGLE:
this.endRectangleSelection();
break;
case OperationType.LASSO_ELLIPSE:
this.endEllipseSelection();
break;
case OperationType.LASSO:
this.endFreeSelection();
break;
}
// 如果有命令管理器,使用命令模式记录选区创建
if (this.commandManager && this.selectionObject) {
this.commandManager.execute(
new CreateSelectionCommand({
canvas: this.canvas,
selectionManager: this,
selectionObject: this.selectionObject,
selectionType: this.selectionType,
})
);
}
};
// 键盘事件处理
this._keyDownHandler = (event) => {
// 只在选区功能激活时处理键盘事件
if (!this.isActive) return;
if (event.key === "Escape") {
// ESC键取消当前选区操作
if (this.drawingObject) {
this.canvas.remove(this.drawingObject);
this.drawingObject = null;
this.startPoint = null;
}
// 清除已有选区
else if (this.selectionObject) {
if (this.commandManager) {
this.commandManager.execute(
new ClearSelectionCommand({
selectionManager: this,
})
);
} else {
this.clearSelection();
}
}
}
};
// 添加事件监听
this.canvas.on("mouse:down", this._mouseDownHandler);
this.canvas.on("mouse:move", this._mouseMoveHandler);
this.canvas.on("mouse:up", this._mouseUpHandler);
// 添加键盘事件监听
document.addEventListener("keydown", this._keyDownHandler);
}
/**
* 清理事件监听
*/
cleanupEvents() {
if (!this.canvas) return;
// 移除事件监听
if (this._mouseDownHandler) {
this.canvas.off("mouse:down", this._mouseDownHandler);
this._mouseDownHandler = null;
}
if (this._mouseMoveHandler) {
this.canvas.off("mouse:move", this._mouseMoveHandler);
this._mouseMoveHandler = null;
}
if (this._mouseUpHandler) {
this.canvas.off("mouse:up", this._mouseUpHandler);
this._mouseUpHandler = null;
}
if (this._keyDownHandler) {
document.removeEventListener("keydown", this._keyDownHandler);
this._keyDownHandler = null;
}
}
/**
* 获取选区对象
* @returns {Object} 选区对象
*/
getSelectionObject() {
return this.selectionObject;
}
/**
* 获取选区路径
* @returns {Array|String} 选区路径数据
*/
getSelectionPath() {
return this.selectionPath;
}
/**
* 获取羽化值
* @returns {Number} 羽化值
*/
getFeatherAmount() {
return this.featherAmount;
}
/**
* 设置羽化值
* @param {Number} amount 羽化值
*/
setFeatherAmount(amount) {
this.featherAmount = amount;
return this.updateSelectionAppearance();
}
/**
* 设置选区对象
* @param {Object} object 选区对象
*/
setSelectionObject(object) {
// 如果已存在选区,先移除
if (this.selectionObject) {
this.removeSelectionFromCanvas();
}
// 更新选区对象
this.selectionObject = object;
this.selectionPath = object.path;
this.selectionId = object.id || generateId();
// 更新外观
this.updateSelectionAppearance();
// 添加到画布(确保在顶层)
if (this.canvas && this.selectionObject) {
this.canvas.add(this.selectionObject);
this.canvas.bringToFront(this.selectionObject);
this.canvas.renderAll();
}
// 触发选区变化回调
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
this.onSelectionChanged();
}
return true;
}
/**
* 从路径数据设置选区
* @param {Array|String} path 选区路径数据
*/
setSelectionFromPath(path) {
if (!path) return false;
// 创建选区对象
const selectionObj = new fabric.Path(path, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
});
// 设置选区
return this.setSelectionObject(selectionObj);
}
/**
* 更新选区外观
*/
updateSelectionAppearance() {
if (!this.selectionObject) return false;
// 应用基本样式
Object.assign(this.selectionObject, this.selectionStyle);
// 应用羽化效果
if (this.featherAmount > 0) {
this.selectionObject.shadow = new fabric.Shadow({
color: "rgba(0, 150, 255, 0.5)",
blur: this.featherAmount,
offsetX: 0,
offsetY: 0,
});
} else {
this.selectionObject.shadow = null;
}
// 更新画布
this.canvas.renderAll();
return true;
}
/**
* 移除选区
*/
removeSelectionFromCanvas() {
if (this.canvas && this.selectionObject) {
this.canvas.remove(this.selectionObject);
this.canvas.renderAll();
}
}
/**
* 清除选区
*/
clearSelection() {
// 移除选区对象
this.removeSelectionFromCanvas();
// 重置选区状态
this.selectionObject = null;
this.selectionPath = null;
this.selectionId = null;
this.featherAmount = 0;
// 触发选区变化回调
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
this.onSelectionChanged();
}
return true;
}
/**
* 反转选区
*/
async invertSelection() {
if (!this.canvas || !this.selectionObject) return false;
// 获取画布范围
const canvasRect = new fabric.Rect({
left: 0,
top: 0,
width: this.canvas.width,
height: this.canvas.height,
selectable: false,
});
// 创建反选路径
let invertedPath;
try {
invertedPath = canvasRect.subtractPathFromRect(this.selectionObject.path);
} catch (error) {
console.error("无法反转选区:", error);
return false;
}
// 设置新的选区
const newSelection = new fabric.Path(invertedPath.path, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
});
return this.setSelectionObject(newSelection);
}
/**
* 添加到选区
* @param {Object} newSelection 要添加的选区对象
*/
async addToSelection(newSelection) {
if (!this.canvas) return false;
// 如果当前没有选区,直接使用新选区
if (!this.selectionObject) {
return this.setSelectionObject(newSelection);
}
// 合并选区
let combinedPath;
try {
combinedPath = this.selectionObject.union(newSelection);
} catch (error) {
console.error("无法添加到选区:", error);
return false;
}
// 设置新的选区
const combinedSelection = new fabric.Path(combinedPath.path, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
});
return this.setSelectionObject(combinedSelection);
}
/**
* 从选区中移除
* @param {Object} removeSelection 要移除的选区对象
*/
async removeFromSelection(removeSelection) {
if (!this.canvas || !this.selectionObject) return false;
// 从当前选区中减去新选区
let resultPath;
try {
resultPath = this.selectionObject.subtract(removeSelection);
} catch (error) {
console.error("无法从选区中移除:", error);
return false;
}
// 设置新的选区
const newSelection = new fabric.Path(resultPath.path, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
});
return this.setSelectionObject(newSelection);
}
/**
* 应用羽化效果
* @param {Number} amount 羽化值
*/
async featherSelection(amount) {
if (!this.selectionObject) return false;
// 更新羽化值
this.featherAmount = amount;
// 更新选区外观
return this.updateSelectionAppearance();
}
/**
* 检查对象是否在选区内
* @param {Object} object 要检查的对象
* @returns {Boolean} 是否在选区内
*/
isObjectInSelection(object) {
if (!this.selectionObject || !object) return false;
// 获取对象的边界框
const bounds = object.getBoundingRect();
const { left, top, width, height } = bounds;
// 检查对象的中心点和四个角是否在选区内
const centerX = left + width / 2;
const centerY = top + height / 2;
// 检查中心点
if (this.isPointInSelection(centerX, centerY)) return true;
// 检查四个角
if (this.isPointInSelection(left, top)) return true;
if (this.isPointInSelection(left + width, top)) return true;
if (this.isPointInSelection(left, top + height)) return true;
if (this.isPointInSelection(left + width, top + height)) return true;
return false;
}
/**
* 检查点是否在选区内
* @param {Number} x X坐标
* @param {Number} y Y坐标
* @returns {Boolean} 是否在选区内
*/
isPointInSelection(x, y) {
if (!this.selectionObject) return false;
// 使用fabric.js的containsPoint方法判断点是否在选区内
return this.selectionObject.containsPoint({ x, y });
}
/**
* 开始自由选区
* @param {Object} options 事件对象
*/
startFreeSelection(options) {
if (!this.canvas || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
this.startPoint = pointer;
// 创建用于绘制轨迹的点数组
this.drawingPoints = [pointer];
// 初始化SVG路径字符串
this.currentPathString = `M ${pointer.x} ${pointer.y}`;
// 创建临时路径对象用于实时显示
this.drawingObject = new fabric.Path(this.currentPathString, {
stroke: this.selectionStyle.stroke,
strokeWidth: this.selectionStyle.strokeWidth,
strokeDashArray: this.selectionStyle.strokeDashArray,
fill: "transparent",
selectable: false,
evented: false,
strokeLineCap: "round",
strokeLineJoin: "round",
});
// 添加到画布
this.canvas.add(this.drawingObject);
this.canvas.renderAll();
}
/**
* 绘制自由选区
* @param {Object} options 事件对象
*/
drawFreeSelection(options) {
if (!this.drawingObject || !this.drawingPoints || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
// 添加新的点,但避免添加过于密集的点
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
const distance = Math.sqrt(
Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2)
);
// 只有当距离大于2像素时才添加新点避免路径过于复杂
if (distance > 2) {
this.drawingPoints.push(pointer);
// 更新路径字符串
this.currentPathString += ` L ${pointer.x} ${pointer.y}`;
// 移除旧的绘制对象
this.canvas.remove(this.drawingObject);
// 创建新的路径对象
this.drawingObject = new fabric.Path(this.currentPathString, {
stroke: this.selectionStyle.stroke,
strokeWidth: this.selectionStyle.strokeWidth,
strokeDashArray: this.selectionStyle.strokeDashArray,
fill: "transparent",
selectable: false,
evented: false,
strokeLineCap: "round",
strokeLineJoin: "round",
});
// 重新添加到画布
this.canvas.add(this.drawingObject);
this.canvas.renderAll();
}
}
/**
* 结束自由选区
*/
endFreeSelection() {
if (!this.drawingObject || !this.drawingPoints || !this.isActive) return;
// 检查是否有足够的点来形成选区
if (this.drawingPoints.length < 3) {
// 点太少,清除绘制对象
this.canvas.remove(this.drawingObject);
this.drawingObject = null;
this.drawingPoints = null;
this.startPoint = null;
this.currentPathString = null;
return;
}
// 自动闭合路径 - 连接最后一点到第一点
const firstPoint = this.drawingPoints[0];
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
const closingDistance = Math.sqrt(
Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2)
);
// 如果首尾距离较大,自动添加闭合线段
let finalPathString = this.currentPathString;
if (closingDistance > 10) {
finalPathString += ` L ${firstPoint.x} ${firstPoint.y}`;
}
finalPathString += " Z"; // 闭合路径
// 创建最终选区对象
const selectionObj = new fabric.Path(finalPathString, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
fill: this.selectionStyle.fill, // 恢复填充
});
// 移除绘制中的临时对象
this.canvas.remove(this.drawingObject);
// 重置绘制状态
this.drawingObject = null;
this.drawingPoints = null;
this.startPoint = null;
this.currentPathString = null;
// 设置选区
this.setSelectionObject(selectionObj);
}
/**
* 开始矩形选区
* @param {Object} options 事件对象
*/
startRectangleSelection(options) {
if (!this.canvas || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
this.startPoint = pointer;
// 创建矩形对象
this.drawingObject = new fabric.Rect({
left: pointer.x,
top: pointer.y,
width: 0,
height: 0,
...this.selectionStyle,
fill: "transparent", // 在绘制过程中不显示填充
});
// 添加到画布
this.canvas.add(this.drawingObject);
this.canvas.renderAll();
}
/**
* 绘制矩形选区
* @param {Object} options 事件对象
*/
drawRectangleSelection(options) {
if (!this.drawingObject || !this.startPoint || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
// 计算宽度和高度
const width = Math.abs(pointer.x - this.startPoint.x);
const height = Math.abs(pointer.y - this.startPoint.y);
// 确定左上角坐标
const left = Math.min(this.startPoint.x, pointer.x);
const top = Math.min(this.startPoint.y, pointer.y);
// 更新矩形
this.drawingObject.set({
left: left,
top: top,
width: width,
height: height,
});
this.canvas.renderAll();
}
/**
* 结束矩形选区
*/
endRectangleSelection() {
if (!this.drawingObject || !this.startPoint || !this.isActive) return;
// 将矩形转换为路径
const left = this.drawingObject.left;
const top = this.drawingObject.top;
const width = this.drawingObject.width;
const height = this.drawingObject.height;
// 如果矩形太小,忽略
if (width < 5 || height < 5) {
this.canvas.remove(this.drawingObject);
this.drawingObject = null;
this.startPoint = null;
return;
}
// 创建矩形路径字符串
const pathString = `M ${left} ${top} L ${left + width} ${top} L ${
left + width
} ${top + height} L ${left} ${top + height} Z`;
// 创建最终选区对象
const selectionObj = new fabric.Path(pathString, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
fill: this.selectionStyle.fill, // 恢复填充
});
// 移除绘制中的临时对象
this.canvas.remove(this.drawingObject);
// 重置绘制状态
this.drawingObject = null;
this.startPoint = null;
// 设置选区
this.setSelectionObject(selectionObj);
}
/**
* 开始椭圆选区
* @param {Object} options 事件对象
*/
startEllipseSelection(options) {
if (!this.canvas || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
this.startPoint = pointer;
// 创建椭圆对象
this.drawingObject = new fabric.Ellipse({
left: pointer.x,
top: pointer.y,
rx: 0,
ry: 0,
...this.selectionStyle,
fill: "transparent", // 在绘制过程中不显示填充
// originX: "left",
// originY: "top",
originX: "center",
originY: "center",
});
// 添加到画布
this.canvas.add(this.drawingObject);
this.canvas.renderAll();
}
/**
* 绘制椭圆选区
* @param {Object} options 事件对象
*/
drawEllipseSelection(options) {
if (!this.drawingObject || !this.startPoint || !this.isActive) return;
// 获取鼠标位置
const pointer = this.canvas.getPointer(options.e);
// 计算半径
const rx = Math.abs(pointer.x - this.startPoint.x) / 2;
const ry = Math.abs(pointer.y - this.startPoint.y) / 2;
// 确定中心坐标
const left = Math.min(this.startPoint.x, pointer.x);
const top = Math.min(this.startPoint.y, pointer.y);
// 更新椭圆
this.drawingObject.set({
left: left,
top: top,
rx: rx,
ry: ry,
originX: "left",
originY: "top",
});
this.canvas.renderAll();
}
/**
* 结束椭圆选区
*/
endEllipseSelection() {
if (!this.drawingObject || !this.startPoint || !this.isActive) return;
// 获取椭圆参数
const { left, top, rx, ry } = this.drawingObject;
// 如果椭圆太小,忽略
if (rx < 2 || ry < 2) {
this.canvas.remove(this.drawingObject);
this.drawingObject = null;
this.startPoint = null;
return;
}
// 计算中心点
const cx = left + rx;
const cy = top + ry;
// 将椭圆转换为路径字符串
const pathString = this.ellipseToSVGPath(cx, cy, rx, ry);
// 创建最终选区对象
const selectionObj = new fabric.Path(pathString, {
...this.selectionStyle,
id: `selection_${Date.now()}`,
name: "selection",
fill: this.selectionStyle.fill, // 恢复填充
});
// 移除绘制中的临时对象
this.canvas.remove(this.drawingObject);
// 重置绘制状态
this.drawingObject = null;
this.startPoint = null;
// 设置选区
this.setSelectionObject(selectionObj);
}
/**
* 将椭圆转换为SVG路径字符串
* @param {Number} cx 中心点X坐标
* @param {Number} cy 中心点Y坐标
* @param {Number} rx X半径
* @param {Number} ry Y半径
* @returns {String} SVG路径字符串
*/
ellipseToSVGPath(cx, cy, rx, ry) {
// 使用椭圆弧命令创建完整椭圆
return `M ${cx - rx} ${cy} A ${rx} ${ry} 0 1 0 ${
cx + rx
} ${cy} A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`;
}
/**
* 设置选区工具
* @param {string} type 选区类型OperationType.LASSO, OperationType.LASSO_RECTANGLE, OperationType.LASSO_ELLIPSE
*/
setSelectionType(type) {
this.selectionType = type;
// 如果正在绘制,清除临时对象
if (this.drawingObject) {
this.canvas.remove(this.drawingObject);
this.drawingObject = null;
this.startPoint = null;
}
}
/**
* 设置选区工具的鼠标事件
*/
setupSelectionEvents() {
// 选区事件现在通过 setCurrentTool 方法管理
// 这个方法现在主要用于刷新或重置事件监听
if (!this.canvas || !this.isActive) return;
// 确保选区处于激活状态
if (this.tools.includes(this.currentTool)) {
this.isActive = true;
// 如果事件还没有初始化,初始化它们
if (!this._mouseDownHandler) {
this.initEvents();
}
}
}
/**
* 清理资源
*/
dispose() {
this.cleanupEvents();
this.clearSelection();
this.canvas = null;
this.commandManager = null;
this.layerManager = null;
}
}

View File

@@ -190,30 +190,26 @@ export class ToolManager {
[OperationType.PART]: {
name: "部件选取工具",
icon: "part",
cursor: "crosshair",
// setup: this.setupLassoTool.bind(this),
specialLayerDisabled: true,
cursor: "default",
setup: this.setupPartTool.bind(this),
},
[OperationType.PART_RECTANGLE]: {
name: "部件选取工具-矩形",
icon: "part",
cursor: "crosshair",
// setup: this.setupRectangleLassoTool.bind(this),
specialLayerDisabled: true,
cursor: "default",
setup: this.setupPartTool.bind(this),
},
[OperationType.PART_BRUSH]: {
name: "部件选取工具-画笔",
icon: "part",
cursor: "crosshair",
// setup: this.setupEllipseLassoTool.bind(this),
specialLayerDisabled: true,
cursor: "default",
setup: this.setupPartTool.bind(this),
},
[OperationType.PART_ERASER]: {
name: "部件选取工具-橡皮擦",
icon: "part",
cursor: "crosshair",
// setup: this.setupEllipseLassoTool.bind(this),
specialLayerDisabled: true,
cursor: "default",
setup: this.setupPartTool.bind(this),
},
// 红绿图模式专用工具
@@ -704,6 +700,20 @@ export class ToolManager {
}
}
/**
* 设置部件选取工具
*/
setupPartTool() {
if (!this.canvas) return;
if (this.checkToolCanOperateSelectedObject()) return;
this.canvas.isDrawingMode = false;
this.canvas.selection = false;
if (this.canvasManager && this.canvasManager.partManager) {
this.canvasManager.partManager.setCurrentTool(OperationType.PART);
}
}
/**
* 设置波浪工具
*/