n8n-nodes-sudomock 0.16.0 → 0.17.0

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 CHANGED
@@ -287,6 +287,10 @@ Generate a mockup by filling smart objects with your designs.
287
287
  - Brightness: -150 to 150
288
288
  - Contrast: -100 to 100
289
289
  - Opacity: 0-100
290
+ - Custom Width and Custom Height: the artwork's own size in pixels
291
+ - Position Top and Position Left: offset in pixels from the top left of the smart object when Custom Width or Custom Height is set. Without a size they move the whole smart object on the template, so set them together with a size.
292
+ - Tile: repeat the artwork as a pattern across the whole smart object. Custom Width and Custom Height are one tile, Fit Mode places the artwork inside each tile, and Position Top and Position Left set where the grid starts. A tile needs a size.
293
+ - Tile Spacing: gap between tiles, 0 to 100 percent of the tile size: that percent of the tile width between columns, and of its height between rows. Used only when Tile is on.
290
294
 
291
295
  - **Hidden Layers** (optional): JSON array of up to 50 layer UUIDs from **PSD Mockup: Get Layers** to leave out of this render, including layers inside a smart object. Hiding a group hides every layer inside it. Works on its own.
292
296
 
@@ -387,9 +391,9 @@ Deletion confirmation
387
391
  Create, prepare, render, and manage reusable mockups from product images without a PSD.
388
392
 
389
393
  1. **Photo Mockup: Create** accepts a public `source_url` or `source_base64`, plus an optional name. By default it waits and returns the ready mockup detail unchanged. Enable **Run Asynchronously** to receive a 202 job with `job_id` and `status_url`, then use **Get Job** when needed.
390
- 2. **Photo Mockup: Get** returns `draft`, `ready`, or `failed` status. Poll this operation until the mockup is `ready`. The ready response lists every printable product in `data.surfaces` (addressed by `surface_uuid`), and every print area someone has drawn in `data.quads` (addressed by `print_area_id`).
394
+ 2. **Photo Mockup: Get** returns `draft`, `ready`, or `failed` status. Poll this operation until the mockup is `ready`. The ready response lists every printable product, and the main wall of a room photo, in `data.surfaces` (addressed by `surface_uuid`), and every print area someone has drawn in `data.quads` (addressed by `print_area_id`).
391
395
  3. **Photo Mockup: List** returns your photo mockups with limit, offset, and shopper-customizable filtering.
392
- 4. **Photo Mockup: Set Print Areas** replaces saved areas with quads made from four `[x, y]` coordinate pairs. It can also send an empty list; the API remains authoritative and accepts that only when the mockup can remain renderable without saved areas.
396
+ 4. **Photo Mockup: Set Print Areas** replaces saved areas with quads made from four `[x, y]` coordinate pairs. It can also send an empty list; the API remains authoritative and accepts that only when the mockup can remain renderable without saved areas. An area with a Print Area ID, the id of one of the mockup's print areas, keeps that id; an area without one gets a new id.
393
397
  5. **Photo Mockup: Render** accepts the mockup UUID and one or more ready render targets. Each target names exactly one address: `uuid` for a saved print area, or `surface_uuid` for a surface. Each target also includes either an artwork URL or Base64 artwork, with optional background removal (adds 25 credits per artwork), color, public adjustments, and placement. Export options support PNG, JPG, or WebP from 100 to 10000 pixels, plus quality and optional DPI. Each render costs 5 credits and returns `print_files`, where `print_files[0]` is the CDN URL.
394
398
  6. **Photo Mockup: Delete** deletes the selected photo mockup.
395
399
 
@@ -399,8 +403,9 @@ logo can both be addressed on the same product, in the same call, as two targets
399
403
 
400
404
  Placement is where the two kinds differ:
401
405
 
402
- - A **surface** target takes **Coverage**, a percentage from 10 to 100. Leave it out
403
- and the artwork covers the whole surface.
406
+ - A **surface** target takes either **Coverage**, a percentage from 10 to 100, or an
407
+ explicit **Width** and **Height** pair. Send one or the other, never both. Leave
408
+ both out and the artwork covers the whole surface.
404
409
  - A **print area** target takes either **Fit** (`contain`, `fill`, or `cover`), which
405
410
  always sizes the artwork to the whole area, or an explicit **Width** and **Height**
406
411
  pair when you want to choose the box yourself. Send one or the other, never both,
@@ -409,6 +414,15 @@ Placement is where the two kinds differ:
409
414
  Both kinds take position, offset, and rotation. Anything you do not set is not sent,
410
415
  so the API decides it.
411
416
 
417
+ Both kinds also take **Tile**, which repeats the artwork as a pattern across the
418
+ whole target. The size you set is one tile: **Fit**, or **Width** and **Height**, on
419
+ a print area, and **Coverage**, or **Width** and **Height**, on a surface. **Position**
420
+ with **Offset X** and **Offset Y** sets where the grid starts. **Tile Spacing** is the
421
+ gap between tiles, 0 to 100 percent of the tile size, used only when Tile is on.
422
+
423
+ Each entry in `data.surfaces` also carries a `label`, what was detected there, such
424
+ as `pillow` or `wall`, so you can tell surfaces apart before you pick one.
425
+
412
426
  Create must reach `ready` through **Photo Mockup: Get** before **Photo Mockup: Render**.
413
427
 
414
428
  ---
@@ -421,7 +435,7 @@ Turn a mockup (or an existing image URL) into a short product video. Always asyn
421
435
 
422
436
  - **Input Mode**: `Render Mockup` (render a mockup with your designs first) or `Animate Image URL` (animate a public image directly)
423
437
  - **Mockup UUID** (required in Render Mockup mode): UUID of the mockup to animate
424
- - **Smart Objects** (required in Render Mockup mode): one or more smart objects with a Design URL and Fit Mode
438
+ - **Smart Objects** (required in Render Mockup mode): one or more smart objects with a Design URL and Fit Mode, plus optional Custom Width, Custom Height, Position Top, Position Left, Tile and Tile Spacing, the same as in PSD Mockup: Render
425
439
  - **Image URL** (required in Animate Image URL mode): public HTTPS image (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif`)
426
440
  - **Webhook URL** (optional): one-off HTTPS URL notified when this job finishes
427
441
  - **Video Options**:
@@ -5,7 +5,7 @@ const n8n_workflow_1 = require("n8n-workflow");
5
5
  const webhooks_1 = require("./webhooks");
6
6
  const resources_1 = require("./resources");
7
7
  const TERMINAL_JOB_STATUSES = ['succeeded', 'failed', 'cancelled'];
8
- function format2DPrintAreas(printAreas) {
8
+ function format2DPrintAreas(printAreas, keepIds = false) {
9
9
  return printAreas.map((area) => ({
10
10
  points: [
11
11
  [area.point1X, area.point1Y],
@@ -13,8 +13,102 @@ function format2DPrintAreas(printAreas) {
13
13
  [area.point3X, area.point3Y],
14
14
  [area.point4X, area.point4Y],
15
15
  ],
16
+ // Only Set Print Areas keeps an id: a mockup being created has no area to keep.
17
+ ...(keepIds && area.printAreaId ? { print_area_id: area.printAreaId } : {}),
16
18
  }));
17
19
  }
20
+ // The size, position and tile fields of a smart object asset. PSD Mockup: Render
21
+ // and Render Video both offer them, so the two show the same names and descriptions.
22
+ const ASSET_PLACEMENT_FIELDS = {
23
+ sizeHeight: {
24
+ displayName: 'Custom Height',
25
+ name: 'sizeHeight',
26
+ type: 'number',
27
+ typeOptions: {
28
+ minValue: 1,
29
+ },
30
+ default: 0,
31
+ description: 'Custom asset height override in pixels. 0 = use smart object bounds.',
32
+ },
33
+ sizeWidth: {
34
+ displayName: 'Custom Width',
35
+ name: 'sizeWidth',
36
+ type: 'number',
37
+ typeOptions: {
38
+ minValue: 1,
39
+ },
40
+ default: 0,
41
+ description: 'Custom asset width override in pixels. 0 = use smart object bounds.',
42
+ },
43
+ positionLeft: {
44
+ displayName: 'Position Left',
45
+ name: 'positionLeft',
46
+ type: 'number',
47
+ default: null,
48
+ description: 'Custom position left offset in pixels. Leave empty for the default placement; 0 is the left edge.',
49
+ },
50
+ positionTop: {
51
+ displayName: 'Position Top',
52
+ name: 'positionTop',
53
+ type: 'number',
54
+ default: null,
55
+ description: 'Custom position top offset in pixels. Leave empty for the default placement; 0 is the top edge.',
56
+ },
57
+ tile: {
58
+ displayName: 'Tile',
59
+ name: 'tile',
60
+ type: 'boolean',
61
+ default: false,
62
+ description: 'Whether to repeat the artwork as a pattern across the whole smart object. Custom Width and Custom Height are one tile, Fit Mode places the artwork inside each tile, and Position Top and Position Left set where the grid starts. Requires Custom Width or Custom Height.',
63
+ },
64
+ tileSpacing: {
65
+ displayName: 'Tile Spacing',
66
+ name: 'tileSpacing',
67
+ type: 'number',
68
+ typeOptions: {
69
+ minValue: 0,
70
+ maxValue: 100,
71
+ },
72
+ default: 0,
73
+ description: 'Gap between tiles as a percentage of the tile size, 0 to 100. The horizontal gap is that percent of the tile width, the vertical gap that percent of the tile height. Used only when Tile is on.',
74
+ },
75
+ };
76
+ // Puts the size, position and tile fields on a smart object asset. A field left
77
+ // empty is not sent.
78
+ function addAssetPlacement(asset, opts) {
79
+ // Custom size override
80
+ const size = {};
81
+ if (opts.sizeWidth !== undefined && opts.sizeWidth > 0) {
82
+ size.width = opts.sizeWidth;
83
+ }
84
+ if (opts.sizeHeight !== undefined && opts.sizeHeight > 0) {
85
+ size.height = opts.sizeHeight;
86
+ }
87
+ if (Object.keys(size).length > 0) {
88
+ asset.size = size;
89
+ }
90
+ // Custom position override (top/left, DynamicMockups compatible).
91
+ // An empty field holds null and is not sent, so the default
92
+ // placement stays. 0 is sent: it puts the artwork against that edge.
93
+ const position = {};
94
+ if (opts.positionTop !== undefined && opts.positionTop !== null) {
95
+ position.top = opts.positionTop;
96
+ }
97
+ if (opts.positionLeft !== undefined && opts.positionLeft !== null) {
98
+ position.left = opts.positionLeft;
99
+ }
100
+ if (Object.keys(position).length > 0) {
101
+ asset.position = position;
102
+ }
103
+ // Repeat the artwork as a pattern; asset.size is one tile
104
+ if (opts.tile) {
105
+ asset.tile = true;
106
+ }
107
+ // Gap between tiles, as a percent of the tile size
108
+ if (opts.tileSpacing !== undefined && opts.tileSpacing !== 0) {
109
+ asset.tile_spacing = opts.tileSpacing;
110
+ }
111
+ }
18
112
  function parseJsonArray(value, name) {
19
113
  const parsed = typeof value === 'string' ? JSON.parse(value) : value;
20
114
  if (!Array.isArray(parsed)) {
@@ -415,7 +509,7 @@ class SudoMock {
415
509
  },
416
510
  default: {},
417
511
  placeholder: 'Add Print Area',
418
- description: 'Optional during creation. Set Print Areas may send an empty list; the API decides whether the mockup can remain renderable without saved areas.',
512
+ description: 'Optional during creation. Set Print Areas may send an empty list; the API decides whether the mockup can remain renderable without saved areas. On Set Print Areas, an area with a Print Area ID keeps that id.',
419
513
  options: [
420
514
  {
421
515
  name: 'items',
@@ -485,6 +579,19 @@ class SudoMock {
485
579
  default: 0,
486
580
  description: 'Y coordinate of point 4',
487
581
  },
582
+ {
583
+ displayName: 'Print Area ID',
584
+ name: 'printAreaId',
585
+ type: 'string',
586
+ displayOptions: {
587
+ show: {
588
+ '/operation': ['set2DPrintAreas'],
589
+ },
590
+ },
591
+ default: '',
592
+ placeholder: '19be48d4-c810-4420-86e3-7ec2a4d85571',
593
+ description: 'Id of an existing print area of this mockup to keep its id. Leave it out for a new area.',
594
+ },
488
595
  ],
489
596
  },
490
597
  ],
@@ -710,7 +817,15 @@ class SudoMock {
710
817
  name: 'tile',
711
818
  type: 'boolean',
712
819
  default: false,
713
- description: 'Whether to repeat the artwork as a pattern across the whole target. The sizing answer (coverage, fit, or width and height) is one tile, and position with offset_x and offset_y sets where the grid starts. Requires a sizing answer.',
820
+ description: 'Whether to repeat the artwork as a pattern across the whole print area. Fit, or Width and Height, sizes one tile, and Position with Offset X and Offset Y sets where the grid starts. Requires Fit, or Width and Height.',
821
+ },
822
+ {
823
+ displayName: 'Tile Spacing',
824
+ name: 'tile_spacing',
825
+ type: 'number',
826
+ typeOptions: { minValue: 0, maxValue: 100 },
827
+ default: 0,
828
+ description: 'Gap between tiles as a percentage of the tile size, 0 to 100. The horizontal gap is that percent of the tile width, the vertical gap that percent of the tile height. Used only when Tile is on.',
714
829
  },
715
830
  {
716
831
  displayName: 'Width',
@@ -815,7 +930,15 @@ class SudoMock {
815
930
  name: 'tile',
816
931
  type: 'boolean',
817
932
  default: false,
818
- description: 'Whether to repeat the artwork as a pattern across the whole target. The sizing answer (coverage, fit, or width and height) is one tile, and position with offset_x and offset_y sets where the grid starts. Requires a sizing answer.',
933
+ description: 'Whether to repeat the artwork as a pattern across the whole surface. Coverage, or Width and Height, sizes one tile, and Position with Offset X and Offset Y sets where the grid starts. Requires Coverage, or Width and Height.',
934
+ },
935
+ {
936
+ displayName: 'Tile Spacing',
937
+ name: 'tile_spacing',
938
+ type: 'number',
939
+ typeOptions: { minValue: 0, maxValue: 100 },
940
+ default: 0,
941
+ description: 'Gap between tiles as a percentage of the tile size, 0 to 100. The horizontal gap is that percent of the tile width, the vertical gap that percent of the tile height. Used only when Tile is on.',
819
942
  },
820
943
  {
821
944
  displayName: 'Width',
@@ -853,7 +976,7 @@ class SudoMock {
853
976
  {
854
977
  name: 'Surface',
855
978
  value: 'surface',
856
- description: 'Target a printable product returned in data.surfaces',
979
+ description: "Target a printable product, or a room's main wall, returned in data.surfaces",
857
980
  },
858
981
  ],
859
982
  default: 'savedPrintArea',
@@ -1132,26 +1255,8 @@ class SudoMock {
1132
1255
  default: 0,
1133
1256
  description: 'Contrast adjustment (-100 to 100)',
1134
1257
  },
1135
- {
1136
- displayName: 'Custom Height',
1137
- name: 'sizeHeight',
1138
- type: 'number',
1139
- typeOptions: {
1140
- minValue: 1,
1141
- },
1142
- default: 0,
1143
- description: 'Custom asset height override in pixels. 0 = use smart object bounds.',
1144
- },
1145
- {
1146
- displayName: 'Custom Width',
1147
- name: 'sizeWidth',
1148
- type: 'number',
1149
- typeOptions: {
1150
- minValue: 1,
1151
- },
1152
- default: 0,
1153
- description: 'Custom asset width override in pixels. 0 = use smart object bounds.',
1154
- },
1258
+ ASSET_PLACEMENT_FIELDS.sizeHeight,
1259
+ ASSET_PLACEMENT_FIELDS.sizeWidth,
1155
1260
  {
1156
1261
  displayName: 'Opacity',
1157
1262
  name: 'opacity',
@@ -1163,20 +1268,8 @@ class SudoMock {
1163
1268
  default: 100,
1164
1269
  description: 'Layer opacity (0-100)',
1165
1270
  },
1166
- {
1167
- displayName: 'Position Left',
1168
- name: 'positionLeft',
1169
- type: 'number',
1170
- default: null,
1171
- description: 'Custom position left offset in pixels. Leave empty for the default placement; 0 is the left edge.',
1172
- },
1173
- {
1174
- displayName: 'Position Top',
1175
- name: 'positionTop',
1176
- type: 'number',
1177
- default: null,
1178
- description: 'Custom position top offset in pixels. Leave empty for the default placement; 0 is the top edge.',
1179
- },
1271
+ ASSET_PLACEMENT_FIELDS.positionLeft,
1272
+ ASSET_PLACEMENT_FIELDS.positionTop,
1180
1273
  {
1181
1274
  displayName: 'Remove Background',
1182
1275
  name: 'removeBackground',
@@ -1206,13 +1299,8 @@ class SudoMock {
1206
1299
  default: 0,
1207
1300
  description: 'Saturation adjustment (-100 to 100). 0=no change, -100=grayscale.',
1208
1301
  },
1209
- {
1210
- displayName: 'Tile',
1211
- name: 'tile',
1212
- type: 'boolean',
1213
- default: false,
1214
- description: 'Whether to repeat the artwork as a pattern across the whole smart object. size is one tile, fit places the artwork inside each tile, and position sets where the grid starts. Requires size.',
1215
- },
1302
+ ASSET_PLACEMENT_FIELDS.tile,
1303
+ ASSET_PLACEMENT_FIELDS.tileSpacing,
1216
1304
  {
1217
1305
  displayName: 'Vibrance',
1218
1306
  name: 'vibrance',
@@ -1461,6 +1549,14 @@ class SudoMock {
1461
1549
  default: 'crop',
1462
1550
  description: 'How to fit the design in the smart object bounds',
1463
1551
  },
1552
+ {
1553
+ displayName: 'Additional Options',
1554
+ name: 'additionalOptions',
1555
+ type: 'collection',
1556
+ placeholder: 'Add Option',
1557
+ default: {},
1558
+ options: Object.values(ASSET_PLACEMENT_FIELDS),
1559
+ },
1464
1560
  ],
1465
1561
  },
1466
1562
  ],
@@ -2292,7 +2388,7 @@ class SudoMock {
2292
2388
  else if (operation === 'set2DPrintAreas') {
2293
2389
  const mockupUuid = this.getNodeParameter('twoDMockupUuid', i);
2294
2390
  const printAreas = this.getNodeParameter('twoDSetPrintAreas.items', i, []);
2295
- const body = { print_areas: format2DPrintAreas(printAreas) };
2391
+ const body = { print_areas: format2DPrintAreas(printAreas, true) };
2296
2392
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
2297
2393
  method: 'PUT',
2298
2394
  url: `https://api.sudomock.com/api/v1/photo-mockups/${mockupUuid}/print-areas`,
@@ -2592,34 +2688,7 @@ class SudoMock {
2592
2688
  if (opts.removeBackground) {
2593
2689
  asset.remove_background = true;
2594
2690
  }
2595
- // Custom size override
2596
- const size = {};
2597
- if (opts.sizeWidth !== undefined && opts.sizeWidth > 0) {
2598
- size.width = opts.sizeWidth;
2599
- }
2600
- if (opts.sizeHeight !== undefined && opts.sizeHeight > 0) {
2601
- size.height = opts.sizeHeight;
2602
- }
2603
- if (Object.keys(size).length > 0) {
2604
- asset.size = size;
2605
- }
2606
- // Custom position override (top/left, DynamicMockups compatible).
2607
- // An empty field holds null and is not sent, so the default
2608
- // placement stays. 0 is sent: it puts the artwork against that edge.
2609
- const position = {};
2610
- if (opts.positionTop !== undefined && opts.positionTop !== null) {
2611
- position.top = opts.positionTop;
2612
- }
2613
- if (opts.positionLeft !== undefined && opts.positionLeft !== null) {
2614
- position.left = opts.positionLeft;
2615
- }
2616
- if (Object.keys(position).length > 0) {
2617
- asset.position = position;
2618
- }
2619
- // Repeat the artwork as a pattern; asset.size is one tile
2620
- if (opts.tile) {
2621
- asset.tile = true;
2622
- }
2691
+ addAssetPlacement(asset, opts);
2623
2692
  // Color overlay
2624
2693
  if (opts.colorHex) {
2625
2694
  smartObject.color = {
@@ -2731,10 +2800,13 @@ class SudoMock {
2731
2800
  const mockupUuid = this.getNodeParameter('videoMockupUuid', i);
2732
2801
  const smartObjectsData = this.getNodeParameter('videoSmartObjects.items', i, []);
2733
2802
  body.mockup_uuid = mockupUuid;
2734
- body.smart_objects = smartObjectsData.map((so) => ({
2735
- uuid: so.uuid,
2736
- asset: { url: so.assetUrl, fit: so.fit },
2737
- }));
2803
+ body.smart_objects = smartObjectsData.map((so) => {
2804
+ const asset = { url: so.assetUrl, fit: so.fit };
2805
+ if (so.additionalOptions) {
2806
+ addAssetPlacement(asset, so.additionalOptions);
2807
+ }
2808
+ return { uuid: so.uuid, asset };
2809
+ });
2738
2810
  }
2739
2811
  const video = {};
2740
2812
  if (videoOptions.durationSeconds !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sudomock",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "n8n community node for SudoMock mockup rendering API - POD automation",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sudomock.com",