psforge 0.2.0__py3-none-any.whl
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.
- psforge/__init__.py +5 -0
- psforge/app.py +5 -0
- psforge/decorators.py +91 -0
- psforge/ps_adapter/__init__.py +12 -0
- psforge/ps_adapter/action_manager.py +5 -0
- psforge/ps_adapter/application.py +149 -0
- psforge/ps_adapter/context.py +219 -0
- psforge/ps_adapter/process_guard.py +109 -0
- psforge/ps_adapter/utils.py +80 -0
- psforge/registry.py +121 -0
- psforge/resources/__init__.py +3 -0
- psforge/resources/registry.py +3 -0
- psforge/server.py +79 -0
- psforge/tools/__init__.py +3 -0
- psforge/tools/action_tools.py +149 -0
- psforge/tools/adjustment_tools.py +316 -0
- psforge/tools/batch_tools.py +124 -0
- psforge/tools/document_tools.py +341 -0
- psforge/tools/filter_tools.py +252 -0
- psforge/tools/history_tools.py +241 -0
- psforge/tools/image_tools.py +201 -0
- psforge/tools/layer_ordering_tools.py +306 -0
- psforge/tools/layer_properties_tools.py +364 -0
- psforge/tools/layer_tools.py +316 -0
- psforge/tools/layer_transform_tools.py +331 -0
- psforge/tools/mask_tools.py +286 -0
- psforge/tools/registry.py +6 -0
- psforge/tools/selection_tools.py +248 -0
- psforge/tools/session_tools.py +244 -0
- psforge/tools/text_tools.py +390 -0
- psforge-0.2.0.dist-info/METADATA +594 -0
- psforge-0.2.0.dist-info/RECORD +35 -0
- psforge-0.2.0.dist-info/WHEEL +4 -0
- psforge-0.2.0.dist-info/entry_points.txt +3 -0
- psforge-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Mask tools - create, apply, delete layer masks."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from psforge.decorators import debug_tool, log_tool_call
|
|
8
|
+
from psforge.ps_adapter.application import PhotoshopApp
|
|
9
|
+
from psforge.registry import register_tool
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(mcp) -> list[str]:
|
|
13
|
+
"""Register all mask tools with MCP server.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
mcp: MCP server instance.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
List of registered tool names.
|
|
20
|
+
"""
|
|
21
|
+
registered_tools = []
|
|
22
|
+
|
|
23
|
+
@debug_tool
|
|
24
|
+
@log_tool_call
|
|
25
|
+
def create_layer_mask(reveal_all: bool = True) -> dict[str, Any]:
|
|
26
|
+
"""Create a layer mask for the currently active layer.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
reveal_all: If True, create a reveal-all mask (white, shows everything).
|
|
30
|
+
If False, create a hide-all mask (black, hides everything).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
dict: Operation result and context.
|
|
34
|
+
"""
|
|
35
|
+
ps_app = PhotoshopApp()
|
|
36
|
+
doc = ps_app.get_active_document()
|
|
37
|
+
|
|
38
|
+
if not doc:
|
|
39
|
+
return {
|
|
40
|
+
"success": False,
|
|
41
|
+
"error": "No active document",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Check if layer is background
|
|
46
|
+
check_bg_script = """
|
|
47
|
+
(function() {
|
|
48
|
+
try {
|
|
49
|
+
return app.activeDocument.activeLayer.isBackgroundLayer;
|
|
50
|
+
} catch(e) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
"""
|
|
55
|
+
is_background = ps_app.execute_javascript(check_bg_script)
|
|
56
|
+
|
|
57
|
+
if is_background:
|
|
58
|
+
return {
|
|
59
|
+
"success": False,
|
|
60
|
+
"error": "Cannot add mask to background layer. Convert it to a regular layer first.",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Create mask using Action Descriptor (more reliable)
|
|
64
|
+
if reveal_all:
|
|
65
|
+
create_mask_script = """
|
|
66
|
+
var layer = app.activeDocument.activeLayer;
|
|
67
|
+
var layerName = layer.name;
|
|
68
|
+
|
|
69
|
+
// Add reveal-all mask
|
|
70
|
+
var idMk = charIDToTypeID("Mk ");
|
|
71
|
+
var desc = new ActionDescriptor();
|
|
72
|
+
var idNw = charIDToTypeID("Nw ");
|
|
73
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
74
|
+
desc.putClass(idNw, idChnl);
|
|
75
|
+
var idAt = charIDToTypeID("At ");
|
|
76
|
+
var ref = new ActionReference();
|
|
77
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
78
|
+
var idMsk = charIDToTypeID("Msk ");
|
|
79
|
+
ref.putEnumerated(idChnl, idChnl, idMsk);
|
|
80
|
+
desc.putReference(idAt, ref);
|
|
81
|
+
var idUsng = charIDToTypeID("Usng");
|
|
82
|
+
var idUsrM = charIDToTypeID("UsrM");
|
|
83
|
+
var idRvlA = charIDToTypeID("RvlA");
|
|
84
|
+
desc.putEnumerated(idUsng, idUsrM, idRvlA);
|
|
85
|
+
executeAction(idMk, desc, DialogModes.NO);
|
|
86
|
+
|
|
87
|
+
layerName;
|
|
88
|
+
"""
|
|
89
|
+
else:
|
|
90
|
+
create_mask_script = """
|
|
91
|
+
var layer = app.activeDocument.activeLayer;
|
|
92
|
+
var layerName = layer.name;
|
|
93
|
+
|
|
94
|
+
// Add hide-all mask
|
|
95
|
+
var idMk = charIDToTypeID("Mk ");
|
|
96
|
+
var desc = new ActionDescriptor();
|
|
97
|
+
var idNw = charIDToTypeID("Nw ");
|
|
98
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
99
|
+
desc.putClass(idNw, idChnl);
|
|
100
|
+
var idAt = charIDToTypeID("At ");
|
|
101
|
+
var ref = new ActionReference();
|
|
102
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
103
|
+
var idMsk = charIDToTypeID("Msk ");
|
|
104
|
+
ref.putEnumerated(idChnl, idChnl, idMsk);
|
|
105
|
+
desc.putReference(idAt, ref);
|
|
106
|
+
var idUsng = charIDToTypeID("Usng");
|
|
107
|
+
var idUsrM = charIDToTypeID("UsrM");
|
|
108
|
+
var idHdAl = charIDToTypeID("HdAl");
|
|
109
|
+
desc.putEnumerated(idUsng, idUsrM, idHdAl);
|
|
110
|
+
executeAction(idMk, desc, DialogModes.NO);
|
|
111
|
+
|
|
112
|
+
layerName;
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
layer_name = ps_app.execute_javascript(create_mask_script)
|
|
116
|
+
|
|
117
|
+
mask_type = "reveal-all (white)" if reveal_all else "hide-all (black)"
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"success": True,
|
|
121
|
+
"message": f"Created {mask_type} mask for layer '{layer_name}'",
|
|
122
|
+
"layer_name": layer_name,
|
|
123
|
+
"mask_type": mask_type,
|
|
124
|
+
"reveal_all": reveal_all,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Failed to create layer mask: {e}")
|
|
129
|
+
return {
|
|
130
|
+
"success": False,
|
|
131
|
+
"error": str(e),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@debug_tool
|
|
135
|
+
@log_tool_call
|
|
136
|
+
def apply_layer_mask() -> dict[str, Any]:
|
|
137
|
+
"""Apply (flatten) the layer mask of the currently active layer.
|
|
138
|
+
|
|
139
|
+
This permanently applies the mask and removes it.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
dict: Operation result and context.
|
|
143
|
+
"""
|
|
144
|
+
ps_app = PhotoshopApp()
|
|
145
|
+
doc = ps_app.get_active_document()
|
|
146
|
+
|
|
147
|
+
if not doc:
|
|
148
|
+
return {
|
|
149
|
+
"success": False,
|
|
150
|
+
"error": "No active document",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# Check if layer has a mask
|
|
155
|
+
check_mask_script = """
|
|
156
|
+
(function() {
|
|
157
|
+
var layer = app.activeDocument.activeLayer;
|
|
158
|
+
try {
|
|
159
|
+
// Try to access the mask
|
|
160
|
+
var hasMask = layer.layerMaskDensity !== undefined;
|
|
161
|
+
return hasMask;
|
|
162
|
+
} catch(e) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
has_mask = ps_app.execute_javascript(check_mask_script)
|
|
169
|
+
|
|
170
|
+
if not has_mask:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"error": "Active layer does not have a mask",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
apply_mask_script = """
|
|
177
|
+
var layer = app.activeDocument.activeLayer;
|
|
178
|
+
var layerName = layer.name;
|
|
179
|
+
|
|
180
|
+
// Apply layer mask using Action Descriptor
|
|
181
|
+
var idAppr = charIDToTypeID("Appr");
|
|
182
|
+
var desc = new ActionDescriptor();
|
|
183
|
+
var idNull = charIDToTypeID("null");
|
|
184
|
+
var ref = new ActionReference();
|
|
185
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
186
|
+
var idMsk = charIDToTypeID("Msk ");
|
|
187
|
+
ref.putEnumerated(idChnl, idChnl, idMsk);
|
|
188
|
+
desc.putReference(idNull, ref);
|
|
189
|
+
executeAction(idAppr, desc, DialogModes.NO);
|
|
190
|
+
|
|
191
|
+
layerName;
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
layer_name = ps_app.execute_javascript(apply_mask_script)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"success": True,
|
|
198
|
+
"message": f"Applied layer mask on layer '{layer_name}'",
|
|
199
|
+
"layer_name": layer_name,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Failed to apply layer mask: {e}")
|
|
204
|
+
return {
|
|
205
|
+
"success": False,
|
|
206
|
+
"error": str(e),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@debug_tool
|
|
210
|
+
@log_tool_call
|
|
211
|
+
def delete_layer_mask() -> dict[str, Any]:
|
|
212
|
+
"""Delete the layer mask of the currently active layer.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
dict: Operation result and context.
|
|
216
|
+
"""
|
|
217
|
+
ps_app = PhotoshopApp()
|
|
218
|
+
doc = ps_app.get_active_document()
|
|
219
|
+
|
|
220
|
+
if not doc:
|
|
221
|
+
return {
|
|
222
|
+
"success": False,
|
|
223
|
+
"error": "No active document",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Check if layer has a mask
|
|
228
|
+
check_mask_script = """
|
|
229
|
+
(function() {
|
|
230
|
+
var layer = app.activeDocument.activeLayer;
|
|
231
|
+
try {
|
|
232
|
+
var hasMask = layer.layerMaskDensity !== undefined;
|
|
233
|
+
return hasMask;
|
|
234
|
+
} catch(e) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
has_mask = ps_app.execute_javascript(check_mask_script)
|
|
241
|
+
|
|
242
|
+
if not has_mask:
|
|
243
|
+
return {
|
|
244
|
+
"success": False,
|
|
245
|
+
"error": "Active layer does not have a mask",
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
delete_mask_script = """
|
|
249
|
+
var layer = app.activeDocument.activeLayer;
|
|
250
|
+
var layerName = layer.name;
|
|
251
|
+
|
|
252
|
+
// Delete layer mask using Action Descriptor
|
|
253
|
+
var idDlt = charIDToTypeID("Dlt ");
|
|
254
|
+
var desc = new ActionDescriptor();
|
|
255
|
+
var idNull = charIDToTypeID("null");
|
|
256
|
+
var ref = new ActionReference();
|
|
257
|
+
var idChnl = charIDToTypeID("Chnl");
|
|
258
|
+
var idMsk = charIDToTypeID("Msk ");
|
|
259
|
+
ref.putEnumerated(idChnl, idChnl, idMsk);
|
|
260
|
+
desc.putReference(idNull, ref);
|
|
261
|
+
executeAction(idDlt, desc, DialogModes.NO);
|
|
262
|
+
|
|
263
|
+
layerName;
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
layer_name = ps_app.execute_javascript(delete_mask_script)
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
"success": True,
|
|
270
|
+
"message": f"Deleted layer mask from layer '{layer_name}'",
|
|
271
|
+
"layer_name": layer_name,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(f"Failed to delete layer mask: {e}")
|
|
276
|
+
return {
|
|
277
|
+
"success": False,
|
|
278
|
+
"error": str(e),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# Register all tools
|
|
282
|
+
registered_tools.append(register_tool(mcp, create_layer_mask, "create_layer_mask"))
|
|
283
|
+
registered_tools.append(register_tool(mcp, apply_layer_mask, "apply_layer_mask"))
|
|
284
|
+
registered_tools.append(register_tool(mcp, delete_layer_mask, "delete_layer_mask"))
|
|
285
|
+
|
|
286
|
+
return registered_tools
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Selection tools - select all, rectangle selection, deselect, invert selection."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from psforge.decorators import debug_tool, log_tool_call
|
|
8
|
+
from psforge.ps_adapter.application import PhotoshopApp
|
|
9
|
+
from psforge.registry import register_tool
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(mcp) -> list[str]:
|
|
13
|
+
"""Register all selection tools with MCP server.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
mcp: MCP server instance.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
List of registered tool names.
|
|
20
|
+
"""
|
|
21
|
+
registered_tools = []
|
|
22
|
+
|
|
23
|
+
@debug_tool
|
|
24
|
+
@log_tool_call
|
|
25
|
+
def select_all() -> dict[str, Any]:
|
|
26
|
+
"""Select the entire document (all pixels).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
dict: Operation result and context.
|
|
30
|
+
"""
|
|
31
|
+
ps_app = PhotoshopApp()
|
|
32
|
+
doc = ps_app.get_active_document()
|
|
33
|
+
|
|
34
|
+
if not doc:
|
|
35
|
+
return {
|
|
36
|
+
"success": False,
|
|
37
|
+
"error": "No active document",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
select_all_script = """
|
|
42
|
+
var doc = app.activeDocument;
|
|
43
|
+
doc.selection.selectAll();
|
|
44
|
+
|
|
45
|
+
var bounds = doc.selection.bounds;
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
width: parseInt(bounds[2]) - parseInt(bounds[0]),
|
|
48
|
+
height: parseInt(bounds[3]) - parseInt(bounds[1])
|
|
49
|
+
});
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
import json
|
|
53
|
+
|
|
54
|
+
result_str = ps_app.execute_javascript(select_all_script)
|
|
55
|
+
result = json.loads(result_str) if isinstance(result_str, str) else result_str
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"success": True,
|
|
59
|
+
"message": "Selected entire document",
|
|
60
|
+
"selection_width": result["width"],
|
|
61
|
+
"selection_height": result["height"],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Failed to select all: {e}")
|
|
66
|
+
return {
|
|
67
|
+
"success": False,
|
|
68
|
+
"error": str(e),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@debug_tool
|
|
72
|
+
@log_tool_call
|
|
73
|
+
def select_rectangle(top: int, left: int, bottom: int, right: int) -> dict[str, Any]:
|
|
74
|
+
"""Create a rectangular selection.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
top: Top edge position in pixels.
|
|
78
|
+
left: Left edge position in pixels.
|
|
79
|
+
bottom: Bottom edge position in pixels.
|
|
80
|
+
right: Right edge position in pixels.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
dict: Operation result and context.
|
|
84
|
+
"""
|
|
85
|
+
# Validate bounds
|
|
86
|
+
if left >= right:
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"error": "left must be < right",
|
|
90
|
+
}
|
|
91
|
+
if top >= bottom:
|
|
92
|
+
return {
|
|
93
|
+
"success": False,
|
|
94
|
+
"error": "top must be < bottom",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ps_app = PhotoshopApp()
|
|
98
|
+
doc = ps_app.get_active_document()
|
|
99
|
+
|
|
100
|
+
if not doc:
|
|
101
|
+
return {
|
|
102
|
+
"success": False,
|
|
103
|
+
"error": "No active document",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
select_rect_script = f"""
|
|
108
|
+
var doc = app.activeDocument;
|
|
109
|
+
|
|
110
|
+
// Create selection region
|
|
111
|
+
var selRegion = [
|
|
112
|
+
[{left}, {top}],
|
|
113
|
+
[{right}, {top}],
|
|
114
|
+
[{right}, {bottom}],
|
|
115
|
+
[{left}, {bottom}]
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
doc.selection.select(selRegion);
|
|
119
|
+
|
|
120
|
+
"Rectangle selected";
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
ps_app.execute_javascript(select_rect_script)
|
|
124
|
+
|
|
125
|
+
width = right - left
|
|
126
|
+
height = bottom - top
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"success": True,
|
|
130
|
+
"message": f"Created rectangular selection ({width}x{height}px)",
|
|
131
|
+
"bounds": {"top": top, "left": left, "bottom": bottom, "right": right},
|
|
132
|
+
"width": width,
|
|
133
|
+
"height": height,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Failed to create rectangle selection: {e}")
|
|
138
|
+
return {
|
|
139
|
+
"success": False,
|
|
140
|
+
"error": str(e),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@debug_tool
|
|
144
|
+
@log_tool_call
|
|
145
|
+
def deselect() -> dict[str, Any]:
|
|
146
|
+
"""Deselect (remove current selection).
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
dict: Operation result and context.
|
|
150
|
+
"""
|
|
151
|
+
ps_app = PhotoshopApp()
|
|
152
|
+
doc = ps_app.get_active_document()
|
|
153
|
+
|
|
154
|
+
if not doc:
|
|
155
|
+
return {
|
|
156
|
+
"success": False,
|
|
157
|
+
"error": "No active document",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
deselect_script = """
|
|
162
|
+
app.activeDocument.selection.deselect();
|
|
163
|
+
"Selection removed";
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
ps_app.execute_javascript(deselect_script)
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
"success": True,
|
|
170
|
+
"message": "Selection removed",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Failed to deselect: {e}")
|
|
175
|
+
return {
|
|
176
|
+
"success": False,
|
|
177
|
+
"error": str(e),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@debug_tool
|
|
181
|
+
@log_tool_call
|
|
182
|
+
def invert_selection() -> dict[str, Any]:
|
|
183
|
+
"""Invert the current selection (select what was not selected, deselect what was selected).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
dict: Operation result and context.
|
|
187
|
+
"""
|
|
188
|
+
ps_app = PhotoshopApp()
|
|
189
|
+
doc = ps_app.get_active_document()
|
|
190
|
+
|
|
191
|
+
if not doc:
|
|
192
|
+
return {
|
|
193
|
+
"success": False,
|
|
194
|
+
"error": "No active document",
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
invert_script = """
|
|
199
|
+
var doc = app.activeDocument;
|
|
200
|
+
|
|
201
|
+
// Check if there's a selection
|
|
202
|
+
try {
|
|
203
|
+
var bounds = doc.selection.bounds;
|
|
204
|
+
if (!bounds) {
|
|
205
|
+
throw new Error("No selection to invert");
|
|
206
|
+
}
|
|
207
|
+
} catch(e) {
|
|
208
|
+
throw new Error("No selection to invert");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
doc.selection.invert();
|
|
212
|
+
"Selection inverted";
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
result = ps_app.execute_javascript(invert_script)
|
|
216
|
+
|
|
217
|
+
if "No selection" in str(result):
|
|
218
|
+
return {
|
|
219
|
+
"success": False,
|
|
220
|
+
"error": "No selection to invert",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"success": True,
|
|
225
|
+
"message": "Selection inverted",
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
error_msg = str(e)
|
|
230
|
+
if "no selection" in error_msg.lower():
|
|
231
|
+
return {
|
|
232
|
+
"success": False,
|
|
233
|
+
"error": "No selection to invert",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
logger.error(f"Failed to invert selection: {e}")
|
|
237
|
+
return {
|
|
238
|
+
"success": False,
|
|
239
|
+
"error": str(e),
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# Register all tools
|
|
243
|
+
registered_tools.append(register_tool(mcp, select_all, "select_all"))
|
|
244
|
+
registered_tools.append(register_tool(mcp, select_rectangle, "select_rectangle"))
|
|
245
|
+
registered_tools.append(register_tool(mcp, deselect, "deselect"))
|
|
246
|
+
registered_tools.append(register_tool(mcp, invert_selection, "invert_selection"))
|
|
247
|
+
|
|
248
|
+
return registered_tools
|