Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/common/observer-unset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type Item = {
sync?: { enabled: boolean };
unset: (path: string) => unknown;
};

export const unsetLocal = (item: Item, path: string) => {
const enabled = item.sync?.enabled;
if (enabled) item.sync.enabled = false;
item.unset(path);
if (enabled) item.sync.enabled = true;
};
1 change: 1 addition & 0 deletions src/editor-api/external-types/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type EditorSchemaCatalog = {
settings: Record<string, unknown>;
};
assetData: Record<string, Record<string, unknown>>;
assetMeta?: Record<string, Record<string, unknown>>;
[key: string]: unknown;
};

Expand Down
57 changes: 43 additions & 14 deletions src/editor-api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,19 @@ class Schema {
return (this._schema.assetData as Field)[type.toLowerCase()] as Field | undefined;
}

getAssetMeta(type: string) {
if (!isObject(this._schema.assetMeta)) return undefined;
return this._schema.assetMeta[type.toLowerCase()] as Field | undefined;
}

getComponents() {
return this.resolvePath(this.getDocument('scene'), 'entities.*.components', false)?.field as Field;
}

getFields(field: unknown) {
if (!isObject(field)) return {};
return isObject(field.properties) ? field.properties : {};
const value = jsonValue(field);
return isObject(value) && isObject(value.properties) ? value.properties : {};
}

getMapValue(field: unknown) {
Expand All @@ -124,50 +130,70 @@ class Schema {
}

getScope(field: unknown) {
if (!isObject(field)) return undefined;
return field['x-scope'] as string | undefined;
if (isObject(field) && typeof field['x-scope'] === 'string') return field['x-scope'];
const value = jsonValue(field);
return isObject(value) && typeof value['x-scope'] === 'string' ? (value['x-scope'] as string) : undefined;
}

getAssetTypes() {
const asset = this.getDocument('asset');
const type = (asset.properties as Field)?.type;
return ((type as Field)?.enum as string[]) || [];
const asset = jsonValue(this.getDocument('asset'));
if (!isObject(asset) || !isObject(asset.properties)) return [];
const type = jsonValue(asset.properties.type);
return isObject(type) && Array.isArray(type.enum) ? (type.enum as string[]) : [];
}

resolvePath(root: unknown, path: string | readonly (string | number)[], strictArrays = true) {
const parts = typeof path === 'string' ? path.split('.') : path;
let field = root;
let open = false;
let optional = false;

for (const part of parts) {
if (part === '' || !isObject(field)) return null;

field = jsonValue(field);
if (!isObject(field)) return null;
open = false;

if (field['x-open-map'] === true || isObject(field.additionalProperties)) {
open = true;
if (!isObject(field.additionalProperties)) {
return { field: null, default: undefined, hasDefault: false, open };
return { field: null, default: undefined, hasDefault: false, open, optional: true };
}
field = field.additionalProperties;
optional = true;
} else if (field.type === 'array') {
if (strictArrays && (!Number.isInteger(Number(part)) || Number(part) < 0)) return null;
if (!isObject(field.items)) return null;
field = field.items;
optional = false;
} else if (isObject(field.properties) && Object.hasOwn(field.properties, part)) {
optional = !Array.isArray(field.required) || !field.required.includes(part);
field = field.properties[part];
continue;
} else if (!field.type && !field.properties && !field.items && !field.anyOf) {
open = true;
return { field: null, default: undefined, hasDefault: false, open };
return { field: null, default: undefined, hasDefault: false, open, optional: true };
} else {
return null;
}
}

const result = this.getDefault(field);
return { field, default: result.value, hasDefault: result.hasDefault, open };
return { field, default: result.value, hasDefault: result.hasDefault, open, optional };
}

isNullDefault(root: unknown, path: string | readonly (string | number)[]) {
const resolved = this.resolvePath(root, path);
return (
!!resolved &&
!resolved.open &&
resolved.hasDefault &&
resolved.default === null &&
isObject(resolved.field) &&
Array.isArray(resolved.field.anyOf) &&
resolved.field.anyOf.some((item) => isObject(item) && item.type === 'null')
);
}

/**
Expand Down Expand Up @@ -201,14 +227,17 @@ class Schema {

getMergeMethodForPath(root: unknown, path: string, strictArrays = true) {
const field = this.resolvePath(root, path, strictArrays)?.field;
if (!isObject(field)) return undefined;
return field['x-merge-method'] as string | undefined;
if (isObject(field) && typeof field['x-merge-method'] === 'string') {
return field['x-merge-method'];
}
const value = jsonValue(field);
return isObject(value) && typeof value['x-merge-method'] === 'string'
? (value['x-merge-method'] as string)
: undefined;
}

getScopeForPath(root: unknown, path: string, strictArrays = true) {
const field = this.resolvePath(root, path, strictArrays)?.field;
if (!isObject(field)) return undefined;
return field['x-scope'] as string | undefined;
return this.getScope(this.resolvePath(root, path, strictArrays)?.field);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/editor-api/schema/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ class AssetsSchema {
return result;
}

resolveMetaPath(type: string, path: string) {
const schema = this._schemaApi.getAssetMeta(type);
const result = schema ? this._schemaApi.resolvePath(schema, path) : null;
if (result?.hasDefault) {
result.default = typeof result.default === 'function' ? result.default() : utils.deepCopy(result.default);
}
return result;
}

/**
* Gets a list of fields of a particular type for an asset type.
*
Expand Down
10 changes: 5 additions & 5 deletions src/editor-api/schema/scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ class SceneSchema {
_getDefaultData(schema: Field) {
const result: Record<string, unknown> = {};
for (const [key, field] of Object.entries(this._schemaApi.getFields(schema))) {
const value = this._schemaApi.getDefault(field);
if (value.hasDefault) {
result[key] = utils.deepCopy(value.value);
const nested = this._getDefaultData(field as Field);
if (Object.keys(nested).length) {
result[key] = nested;
continue;
}
const nested = this._getDefaultData(field as Field);
if (Object.keys(nested).length) result[key] = nested;
const value = this._schemaApi.getDefault(field);
if (value.hasDefault) result[key] = utils.deepCopy(value.value);
}
return result;
}
Expand Down
12 changes: 7 additions & 5 deletions src/editor-api/schema/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ class SettingsSchema {
_getDefaultData(schema: Field, scope: string) {
const result: Record<string, unknown> = {};
for (const [key, field] of Object.entries(this._schemaApi.getFields(schema))) {
const value = this._schemaApi.getDefault(field);
if (value.hasDefault) {
if (this._schemaApi.getScope(field) === scope) result[key] = utils.deepCopy(value.value);
const nested = this._getDefaultData(field as Field, scope);
if (Object.keys(nested).length) {
result[key] = nested;
continue;
}
const nested = this._getDefaultData(field as Field, scope);
if (Object.keys(nested).length) result[key] = nested;
const value = this._schemaApi.getDefault(field);
if (value.hasDefault && this._schemaApi.getScope(field) === scope) {
result[key] = utils.deepCopy(value.value);
}
}
return result;
}
Expand Down
5 changes: 4 additions & 1 deletion src/editor/animstategraph/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ class AnimStateGraphParameters extends Panel {
conditions[transitionKey] = {};
}
conditions[transitionKey][conditionKey] = condition;
asset.unset(`data.transitions.${transitionKey}.conditions.${conditionKey}.parameterName`);
asset.set(
`data.transitions.${transitionKey}.conditions.${conditionKey}.parameterName`,
null
);
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/editor/animstategraph/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ class AnimStateGraphView {
}
if (state.defaultState) {
state.defaultState = undefined;
this._assets[0].unset(`data.states.${stateKey}.defaultState`);
this._assets[0].set(`data.states.${stateKey}.defaultState`, false);
}
break;
}
Expand Down
17 changes: 17 additions & 0 deletions src/editor/assets/asset-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* An imported material carries its source index. An explicit null means the
* material was created by hand, so "Re-import" does not apply.
*/
export const isImportedMaterial = (asset: any) => {
const value = asset.get('meta.index');
return value !== null && value !== undefined;
};

/**
* texCoord1 is the legacy JSON-model key and is an explicit count (0 = in no
* mesh). TEXCOORD_1 is the GLB-style key inside the free-form render meta map,
* so it stays presence-based.
*/
export const hasUv1 = (asset: any) => {
return (asset.get('meta.attributes.texCoord1') ?? 0) > 0 || asset.has('meta.attributes.TEXCOORD_1');
};
4 changes: 3 additions & 1 deletion src/editor/assets/assets-context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type { AssetObserver } from '@/editor-api';

import { formatShortcut } from '../../common/utils';

import { isImportedMaterial } from './asset-flags';

editor.once('load', () => {
let currentAsset = null;
const legacyScripts = editor.call('settings:project').get('useLegacyScripts');
Expand Down Expand Up @@ -808,7 +810,7 @@ editor.once('load', () => {
!source.get('meta.animation.available')
) {
menuItemReImport.hidden = true;
} else if (currentAsset.get('type') === 'material' && !currentAsset.has('meta.index')) {
} else if (currentAsset.get('type') === 'material' && !isImportedMaterial(currentAsset)) {
menuItemReImport.hidden = true;
} else if (
source.get('type') === 'font' &&
Expand Down
3 changes: 2 additions & 1 deletion src/editor/assets/assets-font-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// depends on the backend honouring `noConvert`, so it does not also produce a target of its own.

import { isRefPath } from '@/common/referenced-font-handler';
import { isReferencedFont } from '@/editor/inspector/assets/font-mode';

type FontDataV3 = {
version: number;
Expand Down Expand Up @@ -365,7 +366,7 @@ editor.once('load', () => {
const missing = Array.from(chars).filter((c) => !v3.chars[c]);
const path = font.get('path') || [];
const parent = path.length ? editor.call('assets:get', path[path.length - 1]) : null;
const migrated = !font.has('data.jsonAsset');
const migrated = !isReferencedFont(font);
const id = font.get('id');

updating.add(id);
Expand Down
10 changes: 6 additions & 4 deletions src/editor/assets/assets-migrate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Observer } from '@playcanvas/observer';

import { unsetLocal } from '@/common/observer-unset';
import { deepEqual, formatter as f } from '@/common/utils';
import { LOAD_SCRIPT_AS_ASSET } from '@/core/constants';
import { isReferencedFont } from '@/editor/inspector/assets/font-mode';

const LEGACY_TINT_PROPERTIES = [
['data.diffuseMapTint', 'data.diffuseTint'],
Expand Down Expand Up @@ -68,7 +70,7 @@ editor.once('load', () => {
].join(' ');
editor.call('console:log:asset', asset, msg);
}
asset.unset(oldPath);
unsetLocal(asset, oldPath);
}
}

Expand Down Expand Up @@ -131,7 +133,7 @@ editor.once('load', () => {

if (asset.has('data.useGamma')) {
const tonemap: boolean = asset.get('data.useGamma') ?? true;
asset.unset('data.useGamma');
unsetLocal(asset, 'data.useGamma');
asset.set('data.useTonemap', tonemap);
const msg = [
`The ${f.path('data.useGamma')} properties of material ${f.asset(asset)} is`,
Expand Down Expand Up @@ -296,7 +298,7 @@ editor.once('load', () => {
// remove fresnelModel since it is now always set to schlick
if (asset.has('data.fresnelModel')) {
const fresnelModel = asset.get('data.fresnelModel');
asset.unset('data.fresnelModel');
unsetLocal(asset, 'data.fresnelModel');
if (fresnelModel !== 2) {
const msg = [
`The ${f.path('data.fresnelModel')} property of material ${f.asset(asset)}`,
Expand Down Expand Up @@ -398,7 +400,7 @@ editor.once('load', () => {
}

// referenced fonts keep intensity in the json descriptor; their data holds only the refs
if (asset.has('data.jsonAsset')) {
if (isReferencedFont(asset)) {
return;
}

Expand Down
6 changes: 3 additions & 3 deletions src/editor/attributes/attributes-panel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Observer } from '@playcanvas/observer';

import { toLinkedFieldValue } from '@/common/pcui/compat-utils';
import { unsetObserver } from '@/editor/driver/observer-unset';

import {
createAssetInput,
Expand Down Expand Up @@ -271,7 +272,7 @@ editor.once('load', () => {

historyState(item, false);
if (items[i].value === undefined) {
item.unset(path);
unsetObserver(item, path);
} else {
item.set(path, items[i].value);
}
Expand All @@ -294,11 +295,10 @@ editor.once('load', () => {

historyState(item, false);
if (value === undefined) {
item.unset(path);
unsetObserver(item, path);
} else {
item.set(path, value);
}
item.set(path, value);
historyState(item, true);
}

Expand Down
Loading
Loading