@ikijs/editor 0.31.0 → 0.32.1
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 +3 -2
- package/dist/index.d.mts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +152 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -7
- 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";
|
|
@@ -1948,7 +1972,8 @@ var PROFILE_VALUES = {
|
|
|
1948
1972
|
hairFollow: 1,
|
|
1949
1973
|
fore: 0.2,
|
|
1950
1974
|
backShare: 0.1,
|
|
1951
|
-
earFar: 0
|
|
1975
|
+
earFar: 0,
|
|
1976
|
+
earFore: 0.1,
|
|
1952
1977
|
nodDown: 0.18,
|
|
1953
1978
|
nodUp: 0.1,
|
|
1954
1979
|
nodLead: 0.06,
|
|
@@ -2015,7 +2040,7 @@ function deriveProfile(v) {
|
|
|
2015
2040
|
earNear: 1,
|
|
2016
2041
|
/** The far ear's width at full turn, about its outer edge, where the head
|
|
2017
2042
|
* leaves it room; less where it does not. */
|
|
2018
|
-
earFarScale: 1 - v.
|
|
2043
|
+
earFarScale: 1 - v.earFore
|
|
2019
2044
|
};
|
|
2020
2045
|
const mid = v.nodDown * (1 + v.chinShare) / 2;
|
|
2021
2046
|
const feature = (depth) => ({
|
|
@@ -7058,7 +7083,18 @@ function span2(name, v) {
|
|
|
7058
7083
|
}
|
|
7059
7084
|
|
|
7060
7085
|
// src/auto-rig/custom-parts.ts
|
|
7061
|
-
var KEYS = [
|
|
7086
|
+
var KEYS = [
|
|
7087
|
+
"id",
|
|
7088
|
+
"layer",
|
|
7089
|
+
"anchor",
|
|
7090
|
+
"tie",
|
|
7091
|
+
"after",
|
|
7092
|
+
"deformer",
|
|
7093
|
+
"rigid",
|
|
7094
|
+
"occlusion"
|
|
7095
|
+
];
|
|
7096
|
+
var OCCLUSION_KEYS = ["by", "parameter", "stops"];
|
|
7097
|
+
var STOP_KEYS = ["value", "amount"];
|
|
7062
7098
|
var LAYER_KEYS = ["fileName", "bbox"];
|
|
7063
7099
|
var finite4 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
7064
7100
|
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -7066,7 +7102,7 @@ var inside = (b, [x, y]) => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b
|
|
|
7066
7102
|
var RESERVED = new Set(
|
|
7067
7103
|
ROLE_TABLE.flatMap((r) => [r.role, ...partIdsOfRole(r.role)])
|
|
7068
7104
|
);
|
|
7069
|
-
function checkCustomParts(customParts, byRole, canvas) {
|
|
7105
|
+
function checkCustomParts(customParts, byRole, canvas, parameters, physicsOutputs) {
|
|
7070
7106
|
if (!Array.isArray(customParts)) {
|
|
7071
7107
|
throw new CustomPartError(
|
|
7072
7108
|
`customParts must be an array, got ${customParts === null ? "null" : typeof customParts}`
|
|
@@ -7089,7 +7125,7 @@ function checkCustomParts(customParts, byRole, canvas) {
|
|
|
7089
7125
|
throw new CustomPartError(`${at} must be a plain object`);
|
|
7090
7126
|
}
|
|
7091
7127
|
checkedKeys(at, entry, KEYS, CustomPartError);
|
|
7092
|
-
const { id, layer, anchor, tie, after, deformer, rigid } = entry;
|
|
7128
|
+
const { id, layer, anchor, tie, after, deformer, rigid, occlusion } = entry;
|
|
7093
7129
|
if (typeof id !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
|
|
7094
7130
|
throw new CustomPartError(
|
|
7095
7131
|
`${at}.id must be a plain identifier (letters, digits, underscore; not starting with a digit), got ${JSON.stringify(id)}`
|
|
@@ -7140,9 +7176,84 @@ function checkCustomParts(customParts, byRole, canvas) {
|
|
|
7140
7176
|
`${at}.rigid must be true or false, got ${JSON.stringify(rigid)}`
|
|
7141
7177
|
);
|
|
7142
7178
|
}
|
|
7179
|
+
if (occlusion !== void 0) {
|
|
7180
|
+
checkOcclusion(
|
|
7181
|
+
`${at}.occlusion`,
|
|
7182
|
+
occlusion,
|
|
7183
|
+
present,
|
|
7184
|
+
parameters,
|
|
7185
|
+
physicsOutputs
|
|
7186
|
+
);
|
|
7187
|
+
}
|
|
7143
7188
|
});
|
|
7144
7189
|
return customParts;
|
|
7145
7190
|
}
|
|
7191
|
+
function checkOcclusion(at, occlusion, present, parameters, physicsOutputs) {
|
|
7192
|
+
if (!isObject3(occlusion)) {
|
|
7193
|
+
throw new CustomPartError(`${at} must be a plain object`);
|
|
7194
|
+
}
|
|
7195
|
+
checkedKeys(at, occlusion, OCCLUSION_KEYS, CustomPartError);
|
|
7196
|
+
const { by, parameter, stops } = occlusion;
|
|
7197
|
+
if (!Array.isArray(by) || by.length === 0) {
|
|
7198
|
+
throw new CustomPartError(
|
|
7199
|
+
`${at}.by must be a non-empty array of roles of the set, one of ${present.join(", ")}`
|
|
7200
|
+
);
|
|
7201
|
+
}
|
|
7202
|
+
by.forEach((role, j) => {
|
|
7203
|
+
if (typeof role !== "string" || !present.includes(role)) {
|
|
7204
|
+
throw new CustomPartError(
|
|
7205
|
+
`${at}.by[${j}] must be a role of the set, one of ${present.join(", ")}; got ${JSON.stringify(role)}`
|
|
7206
|
+
);
|
|
7207
|
+
}
|
|
7208
|
+
if (by.indexOf(role) !== j) {
|
|
7209
|
+
throw new CustomPartError(
|
|
7210
|
+
`${at}.by[${j}] "${role}" repeats an earlier role`
|
|
7211
|
+
);
|
|
7212
|
+
}
|
|
7213
|
+
});
|
|
7214
|
+
const descriptor = parameters.find((p) => p.id === parameter);
|
|
7215
|
+
if (typeof parameter !== "string" || descriptor === void 0) {
|
|
7216
|
+
throw new CustomPartError(
|
|
7217
|
+
`${at}.parameter must be a parameter the rig declares, got ${JSON.stringify(parameter)}`
|
|
7218
|
+
);
|
|
7219
|
+
}
|
|
7220
|
+
if (physicsOutputs.has(parameter)) {
|
|
7221
|
+
throw new CustomPartError(
|
|
7222
|
+
`${at}.parameter "${parameter}" is a physics output; an occlusion amount cannot be keyed on one`
|
|
7223
|
+
);
|
|
7224
|
+
}
|
|
7225
|
+
if (!Array.isArray(stops) || stops.length === 0) {
|
|
7226
|
+
throw new CustomPartError(`${at}.stops must be a non-empty array`);
|
|
7227
|
+
}
|
|
7228
|
+
const { min, max } = descriptor;
|
|
7229
|
+
let previous = -Infinity;
|
|
7230
|
+
stops.forEach((stop, k) => {
|
|
7231
|
+
const s = `${at}.stops[${k}]`;
|
|
7232
|
+
if (!isObject3(stop))
|
|
7233
|
+
throw new CustomPartError(`${s} must be a plain object`);
|
|
7234
|
+
checkedKeys(s, stop, STOP_KEYS, CustomPartError);
|
|
7235
|
+
const { value, amount } = stop;
|
|
7236
|
+
if (!finite4(value)) {
|
|
7237
|
+
throw new CustomPartError(
|
|
7238
|
+
`${s}.value must be a finite number, got ${JSON.stringify(value)}`
|
|
7239
|
+
);
|
|
7240
|
+
}
|
|
7241
|
+
if (value < min || value > max) {
|
|
7242
|
+
throw new CustomPartError(
|
|
7243
|
+
`${s}.value ${value} is outside parameter "${parameter}" range [${min},${max}]`
|
|
7244
|
+
);
|
|
7245
|
+
}
|
|
7246
|
+
if (value <= previous) {
|
|
7247
|
+
throw new CustomPartError(`${s}.value must be strictly ascending`);
|
|
7248
|
+
}
|
|
7249
|
+
previous = value;
|
|
7250
|
+
if (!finite4(amount) || amount < 0 || amount > 1) {
|
|
7251
|
+
throw new CustomPartError(
|
|
7252
|
+
`${s}.amount must be a number in 0..1, got ${JSON.stringify(amount)}`
|
|
7253
|
+
);
|
|
7254
|
+
}
|
|
7255
|
+
});
|
|
7256
|
+
}
|
|
7146
7257
|
var CELL_PX2 = 20;
|
|
7147
7258
|
function buildCustomParts(customParts, canvas) {
|
|
7148
7259
|
return customParts.map(({ id, layer }) => {
|
|
@@ -7162,6 +7273,32 @@ function buildCustomParts(customParts, canvas) {
|
|
|
7162
7273
|
};
|
|
7163
7274
|
});
|
|
7164
7275
|
}
|
|
7276
|
+
function writeOcclusion(customParts, built, parts) {
|
|
7277
|
+
const byId = new Map(parts.map((p) => [p.id, p]));
|
|
7278
|
+
customParts.forEach((entry, i) => {
|
|
7279
|
+
if (entry.occlusion === void 0) return;
|
|
7280
|
+
const { by, parameter, stops } = entry.occlusion;
|
|
7281
|
+
const ids = [];
|
|
7282
|
+
by.forEach((role, j) => {
|
|
7283
|
+
for (const id of partIdsOfRole(role)) {
|
|
7284
|
+
const part = byId.get(id);
|
|
7285
|
+
if (part === void 0) continue;
|
|
7286
|
+
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;
|
|
7287
|
+
if (fault !== void 0) {
|
|
7288
|
+
throw new CustomPartError(
|
|
7289
|
+
`customParts[${i}].occlusion.by[${j}] "${role}" yields part "${id}", which ${fault.is}; ${fault.kind} cannot occlude`
|
|
7290
|
+
);
|
|
7291
|
+
}
|
|
7292
|
+
ids.push(id);
|
|
7293
|
+
}
|
|
7294
|
+
});
|
|
7295
|
+
built[i].occlusion = {
|
|
7296
|
+
by: ids,
|
|
7297
|
+
parameter,
|
|
7298
|
+
stops: stops.map((s) => ({ value: s.value, amount: s.amount }))
|
|
7299
|
+
};
|
|
7300
|
+
});
|
|
7301
|
+
}
|
|
7165
7302
|
|
|
7166
7303
|
// src/auto-rig/generate.ts
|
|
7167
7304
|
var MESH_CELLS = {
|
|
@@ -7204,8 +7341,15 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7204
7341
|
const hasNose = has("nose");
|
|
7205
7342
|
const plateHalf = byRole.get("face").cropW / 2;
|
|
7206
7343
|
checkHeadEdges(options.headEdges, byRole);
|
|
7207
|
-
const
|
|
7344
|
+
const physics = has("hair_front") ? hairPhysics(has("hair_back")) : void 0;
|
|
7208
7345
|
const parameters = declareParameters(new Set(byRole.keys()));
|
|
7346
|
+
const entries = options.customParts !== void 0 && !(Array.isArray(options.customParts) && options.customParts.length === 0) ? checkCustomParts(
|
|
7347
|
+
options.customParts,
|
|
7348
|
+
byRole,
|
|
7349
|
+
canvas,
|
|
7350
|
+
parameters,
|
|
7351
|
+
new Set((physics ?? []).map((r) => r.output.parameter))
|
|
7352
|
+
) : [];
|
|
7209
7353
|
const bakes = options.bakes === void 0 ? void 0 : parseBakes(options.bakes, entries, parameters);
|
|
7210
7354
|
checkStrandEdges(
|
|
7211
7355
|
options.strandEdges,
|
|
@@ -7386,6 +7530,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7386
7530
|
parts.forEach((p, i) => {
|
|
7387
7531
|
p.order = i;
|
|
7388
7532
|
});
|
|
7533
|
+
writeOcclusion(entries, built, parts);
|
|
7389
7534
|
if (bakes !== void 0) {
|
|
7390
7535
|
bakeReports = applyBakes(bakes, built, entries, {
|
|
7391
7536
|
canvas,
|
|
@@ -7402,7 +7547,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
|
|
|
7402
7547
|
parameters,
|
|
7403
7548
|
parts,
|
|
7404
7549
|
deformers: deformerList,
|
|
7405
|
-
...
|
|
7550
|
+
...physics !== void 0 ? { physics } : {},
|
|
7406
7551
|
expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
|
|
7407
7552
|
motions: defaultMotions(new Set(parameters.map((p) => p.id)))
|
|
7408
7553
|
};
|