@ikijs/editor 0.31.0 → 0.32.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 +2 -1
- package/dist/index.d.mts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +149 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1023,6 +1023,14 @@ var DeletePart = class {
|
|
|
1023
1023
|
`parts."${this.partId}": cannot delete \u2014 used as a clip mask by part "${masker.id}"; remove its clip first`
|
|
1024
1024
|
);
|
|
1025
1025
|
}
|
|
1026
|
+
const occluded = parts.find(
|
|
1027
|
+
(p) => p.id !== this.partId && p.occlusion?.by.includes(this.partId)
|
|
1028
|
+
);
|
|
1029
|
+
if (occluded) {
|
|
1030
|
+
throw new Error(
|
|
1031
|
+
`parts."${this.partId}": cannot delete \u2014 used as an occluder by part "${occluded.id}"; remove its occlusion first`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1026
1034
|
const i = parts.indexOf(part);
|
|
1027
1035
|
if (!this.captured) {
|
|
1028
1036
|
this.removed = structuredClone(part);
|
|
@@ -1227,6 +1235,14 @@ var SetPartMesh = class {
|
|
|
1227
1235
|
`parts."${this.partId}": cannot remove mesh \u2014 used as a clip mask by part "${masker.id}" (masks require a mesh); remove its clip first`
|
|
1228
1236
|
);
|
|
1229
1237
|
}
|
|
1238
|
+
const occluded = doc.getModel().parts.find(
|
|
1239
|
+
(p) => p.id !== this.partId && p.occlusion?.by.includes(this.partId)
|
|
1240
|
+
);
|
|
1241
|
+
if (occluded) {
|
|
1242
|
+
throw new IkiFormatError(
|
|
1243
|
+
`parts."${this.partId}": cannot remove mesh \u2014 used as an occluder by part "${occluded.id}" (occluders require a mesh); remove its occlusion first`
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1230
1246
|
} else {
|
|
1231
1247
|
if ((part.warps?.length ?? 0) > 0) {
|
|
1232
1248
|
throw new IkiFormatError(
|
|
@@ -1306,6 +1322,14 @@ function validatePhysicsCandidate(doc, candidatePhysics) {
|
|
|
1306
1322
|
}
|
|
1307
1323
|
throw e;
|
|
1308
1324
|
}
|
|
1325
|
+
for (const rig of candidatePhysics) {
|
|
1326
|
+
const part = doc.getModel().parts.find((p) => p.occlusion?.parameter === rig.output.parameter);
|
|
1327
|
+
if (part !== void 0) {
|
|
1328
|
+
throw new IkiFormatError(
|
|
1329
|
+
`physics."${rig.id}".output.parameter "${rig.output.parameter}" keys parts."${part.id}".occlusion; an occlusion amount cannot be keyed on a physics output`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1309
1333
|
}
|
|
1310
1334
|
var AddPhysicsRig = class {
|
|
1311
1335
|
label = "Add physics rig";
|
|
@@ -7058,7 +7082,18 @@ function span2(name, v) {
|
|
|
7058
7082
|
}
|
|
7059
7083
|
|
|
7060
7084
|
// src/auto-rig/custom-parts.ts
|
|
7061
|
-
var KEYS = [
|
|
7085
|
+
var KEYS = [
|
|
7086
|
+
"id",
|
|
7087
|
+
"layer",
|
|
7088
|
+
"anchor",
|
|
7089
|
+
"tie",
|
|
7090
|
+
"after",
|
|
7091
|
+
"deformer",
|
|
7092
|
+
"rigid",
|
|
7093
|
+
"occlusion"
|
|
7094
|
+
];
|
|
7095
|
+
var OCCLUSION_KEYS = ["by", "parameter", "stops"];
|
|
7096
|
+
var STOP_KEYS = ["value", "amount"];
|
|
7062
7097
|
var LAYER_KEYS = ["fileName", "bbox"];
|
|
7063
7098
|
var finite4 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
7064
7099
|
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -7066,7 +7101,7 @@ var inside = (b, [x, y]) => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b
|
|
|
7066
7101
|
var RESERVED = new Set(
|
|
7067
7102
|
ROLE_TABLE.flatMap((r) => [r.role, ...partIdsOfRole(r.role)])
|
|
7068
7103
|
);
|
|
7069
|
-
function checkCustomParts(customParts, byRole, canvas) {
|
|
7104
|
+
function checkCustomParts(customParts, byRole, canvas, parameters, physicsOutputs) {
|
|
7070
7105
|
if (!Array.isArray(customParts)) {
|
|
7071
7106
|
throw new CustomPartError(
|
|
7072
7107
|
`customParts must be an array, got ${customParts === null ? "null" : typeof customParts}`
|
|
@@ -7089,7 +7124,7 @@ function checkCustomParts(customParts, byRole, canvas) {
|
|
|
7089
7124
|
throw new CustomPartError(`${at} must be a plain object`);
|
|
7090
7125
|
}
|
|
7091
7126
|
checkedKeys(at, entry, KEYS, CustomPartError);
|
|
7092
|
-
const { id, layer, anchor, tie, after, deformer, rigid } = entry;
|
|
7127
|
+
const { id, layer, anchor, tie, after, deformer, rigid, occlusion } = entry;
|
|
7093
7128
|
if (typeof id !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
|
|
7094
7129
|
throw new CustomPartError(
|
|
7095
7130
|
`${at}.id must be a plain identifier (letters, digits, underscore; not starting with a digit), got ${JSON.stringify(id)}`
|
|
@@ -7140,9 +7175,84 @@ function checkCustomParts(customParts, byRole, canvas) {
|
|
|
7140
7175
|
`${at}.rigid must be true or false, got ${JSON.stringify(rigid)}`
|
|
7141
7176
|
);
|
|
7142
7177
|
}
|
|
7178
|
+
if (occlusion !== void 0) {
|
|
7179
|
+
checkOcclusion(
|
|
7180
|
+
`${at}.occlusion`,
|
|
7181
|
+
occlusion,
|
|
7182
|
+
present,
|
|
7183
|
+
parameters,
|
|
7184
|
+
physicsOutputs
|
|
7185
|
+
);
|
|
7186
|
+
}
|
|
7143
7187
|
});
|
|
7144
7188
|
return customParts;
|
|
7145
7189
|
}
|
|
7190
|
+
function checkOcclusion(at, occlusion, present, parameters, physicsOutputs) {
|
|
7191
|
+
if (!isObject3(occlusion)) {
|
|
7192
|
+
throw new CustomPartError(`${at} must be a plain object`);
|
|
7193
|
+
}
|
|
7194
|
+
checkedKeys(at, occlusion, OCCLUSION_KEYS, CustomPartError);
|
|
7195
|
+
const { by, parameter, stops } = occlusion;
|
|
7196
|
+
if (!Array.isArray(by) || by.length === 0) {
|
|
7197
|
+
throw new CustomPartError(
|
|
7198
|
+
`${at}.by must be a non-empty array of roles of the set, one of ${present.join(", ")}`
|
|
7199
|
+
);
|
|
7200
|
+
}
|
|
7201
|
+
by.forEach((role, j) => {
|
|
7202
|
+
if (typeof role !== "string" || !present.includes(role)) {
|
|
7203
|
+
throw new CustomPartError(
|
|
7204
|
+
`${at}.by[${j}] must be a role of the set, one of ${present.join(", ")}; got ${JSON.stringify(role)}`
|
|
7205
|
+
);
|
|
7206
|
+
}
|
|
7207
|
+
if (by.indexOf(role) !== j) {
|
|
7208
|
+
throw new CustomPartError(
|
|
7209
|
+
`${at}.by[${j}] "${role}" repeats an earlier role`
|
|
7210
|
+
);
|
|
7211
|
+
}
|
|
7212
|
+
});
|
|
7213
|
+
const descriptor = parameters.find((p) => p.id === parameter);
|
|
7214
|
+
if (typeof parameter !== "string" || descriptor === void 0) {
|
|
7215
|
+
throw new CustomPartError(
|
|
7216
|
+
`${at}.parameter must be a parameter the rig declares, got ${JSON.stringify(parameter)}`
|
|
7217
|
+
);
|
|
7218
|
+
}
|
|
7219
|
+
if (physicsOutputs.has(parameter)) {
|
|
7220
|
+
throw new CustomPartError(
|
|
7221
|
+
`${at}.parameter "${parameter}" is a physics output; an occlusion amount cannot be keyed on one`
|
|
7222
|
+
);
|
|
7223
|
+
}
|
|
7224
|
+
if (!Array.isArray(stops) || stops.length === 0) {
|
|
7225
|
+
throw new CustomPartError(`${at}.stops must be a non-empty array`);
|
|
7226
|
+
}
|
|
7227
|
+
const { min, max } = descriptor;
|
|
7228
|
+
let previous = -Infinity;
|
|
7229
|
+
stops.forEach((stop, k) => {
|
|
7230
|
+
const s = `${at}.stops[${k}]`;
|
|
7231
|
+
if (!isObject3(stop))
|
|
7232
|
+
throw new CustomPartError(`${s} must be a plain object`);
|
|
7233
|
+
checkedKeys(s, stop, STOP_KEYS, CustomPartError);
|
|
7234
|
+
const { value, amount } = stop;
|
|
7235
|
+
if (!finite4(value)) {
|
|
7236
|
+
throw new CustomPartError(
|
|
7237
|
+
`${s}.value must be a finite number, got ${JSON.stringify(value)}`
|
|
7238
|
+
);
|
|
7239
|
+
}
|
|
7240
|
+
if (value < min || value > max) {
|
|
7241
|
+
throw new CustomPartError(
|
|
7242
|
+
`${s}.value ${value} is outside parameter "${parameter}" range [${min},${max}]`
|
|
7243
|
+
);
|
|
7244
|
+
}
|
|
7245
|
+
if (value <= previous) {
|
|
7246
|
+
throw new CustomPartError(`${s}.value must be strictly ascending`);
|
|
7247
|
+
}
|
|
7248
|
+
previous = value;
|
|
7249
|
+
if (!finite4(amount) || amount < 0 || amount > 1) {
|
|
7250
|
+
throw new CustomPartError(
|
|
7251
|
+
`${s}.amount must be a number in 0..1, got ${JSON.stringify(amount)}`
|
|
7252
|
+
);
|
|
7253
|
+
}
|
|
7254
|
+
});
|
|
7255
|
+
}
|
|
7146
7256
|
var CELL_PX2 = 20;
|
|
7147
7257
|
function buildCustomParts(customParts, canvas) {
|
|
7148
7258
|
return customParts.map(({ id, layer }) => {
|
|
@@ -7162,6 +7272,32 @@ function buildCustomParts(customParts, canvas) {
|
|
|
7162
7272
|
};
|
|
7163
7273
|
});
|
|
7164
7274
|
}
|
|
7275
|
+
function writeOcclusion(customParts, built, parts) {
|
|
7276
|
+
const byId = new Map(parts.map((p) => [p.id, p]));
|
|
7277
|
+
customParts.forEach((entry, i) => {
|
|
7278
|
+
if (entry.occlusion === void 0) return;
|
|
7279
|
+
const { by, parameter, stops } = entry.occlusion;
|
|
7280
|
+
const ids = [];
|
|
7281
|
+
by.forEach((role, j) => {
|
|
7282
|
+
for (const id of partIdsOfRole(role)) {
|
|
7283
|
+
const part = byId.get(id);
|
|
7284
|
+
if (part === void 0) continue;
|
|
7285
|
+
const fault = part.clip !== void 0 ? { is: "is clipped", kind: "a clipped part" } : part.mesh === void 0 ? { is: "has no mesh", kind: "a part without a mesh" } : void 0;
|
|
7286
|
+
if (fault !== void 0) {
|
|
7287
|
+
throw new CustomPartError(
|
|
7288
|
+
`customParts[${i}].occlusion.by[${j}] "${role}" yields part "${id}", which ${fault.is}; ${fault.kind} cannot occlude`
|
|
7289
|
+
);
|
|
7290
|
+
}
|
|
7291
|
+
ids.push(id);
|
|
7292
|
+
}
|
|
7293
|
+
});
|
|
7294
|
+
built[i].occlusion = {
|
|
7295
|
+
by: ids,
|
|
7296
|
+
parameter,
|
|
7297
|
+
stops: stops.map((s) => ({ value: s.value, amount: s.amount }))
|
|
7298
|
+
};
|
|
7299
|
+
});
|
|
7300
|
+
}
|
|
7165
7301
|
|
|
7166
7302
|
// src/auto-rig/generate.ts
|
|
7167
7303
|
var MESH_CELLS = {
|
|
@@ -7204,8 +7340,15 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7204
7340
|
const hasNose = has("nose");
|
|
7205
7341
|
const plateHalf = byRole.get("face").cropW / 2;
|
|
7206
7342
|
checkHeadEdges(options.headEdges, byRole);
|
|
7207
|
-
const
|
|
7343
|
+
const physics = has("hair_front") ? hairPhysics(has("hair_back")) : void 0;
|
|
7208
7344
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
7345
|
+
const entries = options.customParts !== void 0 && !(Array.isArray(options.customParts) && options.customParts.length === 0) ? checkCustomParts(
|
|
7346
|
+
options.customParts,
|
|
7347
|
+
byRole,
|
|
7348
|
+
canvas,
|
|
7349
|
+
parameters,
|
|
7350
|
+
new Set((physics ?? []).map((r) => r.output.parameter))
|
|
7351
|
+
) : [];
|
|
7209
7352
|
const bakes = options.bakes === void 0 ? void 0 : parseBakes(options.bakes, entries, parameters);
|
|
7210
7353
|
checkStrandEdges(
|
|
7211
7354
|
options.strandEdges,
|
|
@@ -7386,6 +7529,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7386
7529
|
parts.forEach((p, i) => {
|
|
7387
7530
|
p.order = i;
|
|
7388
7531
|
});
|
|
7532
|
+
writeOcclusion(entries, built, parts);
|
|
7389
7533
|
if (bakes !== void 0) {
|
|
7390
7534
|
bakeReports = applyBakes(bakes, built, entries, {
|
|
7391
7535
|
canvas,
|
|
@@ -7402,7 +7546,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7402
7546
|
parameters,
|
|
7403
7547
|
parts,
|
|
7404
7548
|
deformers: deformerList,
|
|
7405
|
-
...
|
|
7549
|
+
...physics !== void 0 ? { physics } : {},
|
|
7406
7550
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
7407
7551
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
7408
7552
|
};
|