forked from mirror/ObjToSchematic
Minor refactor to exporters
This commit is contained in:
parent
39693c2a42
commit
134914530b
@ -508,7 +508,10 @@ export class AppContext {
|
||||
const filepath = remote.dialog.showSaveDialogSync({
|
||||
title: 'Save structure',
|
||||
buttonLabel: 'Save',
|
||||
filters: [exporter.getFormatFilter()],
|
||||
filters: [{
|
||||
name: exporter.getFormatFilter().name,
|
||||
extensions: [exporter.getFormatFilter().extension],
|
||||
}],
|
||||
});
|
||||
|
||||
if (filepath === undefined) {
|
||||
|
@ -1,23 +1,18 @@
|
||||
import { BlockMesh } from '../block_mesh';
|
||||
import { Vector3 } from '../vector';
|
||||
|
||||
export abstract class IExporter {
|
||||
protected _sizeVector!: Vector3;
|
||||
|
||||
/** The display name of this exporter */
|
||||
public abstract getFormatName(): string;
|
||||
|
||||
/** The file type extension of this exporter
|
||||
/** The file type extension of this exporter.
|
||||
* @note Do not include the dot prefix, e.g. 'obj' not '.obj'.
|
||||
*/
|
||||
public abstract getFileExtension(): string;
|
||||
|
||||
public abstract export(blockMesh: BlockMesh, filePath: string): boolean;
|
||||
|
||||
public getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: [this.getFileExtension()],
|
||||
};
|
||||
public abstract getFormatFilter(): {
|
||||
name: string,
|
||||
extension: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a block mesh to a file.
|
||||
* @param blockMesh The block mesh to export.
|
||||
* @param filePath The location to save the file to.
|
||||
*/
|
||||
public abstract export(blockMesh: BlockMesh, filePath: string): void;
|
||||
}
|
||||
|
@ -3,47 +3,60 @@ import { NBT, TagType } from 'prismarine-nbt';
|
||||
import { BlockMesh } from '../block_mesh';
|
||||
import { AppConstants } from '../constants';
|
||||
import { ceilToNearest } from '../math';
|
||||
import { AppTypes } from '../util';
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { saveNBT } from '../util/nbt_util';
|
||||
import { NBTUtil } from '../util/nbt_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IExporter } from './base_exporter';
|
||||
|
||||
type BlockID = number;
|
||||
type long = [number, number];
|
||||
|
||||
interface BlockMapping {
|
||||
[name: string]: BlockID
|
||||
}
|
||||
type BlockMapping = Map<AppTypes.TNamespacedBlockName, BlockID>;
|
||||
|
||||
export class Litematic extends IExporter {
|
||||
// XZY
|
||||
private _getBufferIndex(vec: Vector3) {
|
||||
return (this._sizeVector.z * this._sizeVector.x * vec.y) + (this._sizeVector.x * vec.z) + vec.x;
|
||||
public override getFormatFilter() {
|
||||
return {
|
||||
name: 'Litematic',
|
||||
extension: 'litematic',
|
||||
};
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string) {
|
||||
const nbt = this._convertToNBT(blockMesh);
|
||||
NBTUtil.save(nbt, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mapping from block names to their respecitve index in the block state palette
|
||||
* Create a mapping from block names to their respecitve index in the block state palette.
|
||||
*/
|
||||
private _createBlockMapping(blockMesh: BlockMesh): BlockMapping {
|
||||
const blockMapping: BlockMapping = { 'minecraft:air': 0 };
|
||||
const blockMapping: BlockMapping = new Map();
|
||||
blockMapping.set('minecraft:air', 0);
|
||||
|
||||
blockMesh.getBlockPalette().forEach((blockName, index) => {
|
||||
blockMapping[blockName] = index + 1;
|
||||
blockMapping.set(blockName, index + 1);
|
||||
});
|
||||
|
||||
return blockMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack the blocks into a buffer that's the dimensions of the block mesh.
|
||||
*/
|
||||
private _createBlockBuffer(blockMesh: BlockMesh, blockMapping: BlockMapping): Uint32Array {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
const bounds = blockMesh.getVoxelMesh()?.getBounds();
|
||||
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
|
||||
|
||||
const buffer = new Uint32Array(bufferSize);
|
||||
const buffer = new Uint32Array(sizeVector.x * sizeVector.y * sizeVector.z);
|
||||
|
||||
blockMesh.getBlocks().forEach((block) => {
|
||||
const indexVector = Vector3.sub(block.voxel.position, bounds.min);
|
||||
const bufferIndex = this._getBufferIndex(indexVector);
|
||||
buffer[bufferIndex] = blockMapping[block.blockInfo.name || 'minecraft:air'];
|
||||
const bufferIndex = (sizeVector.z * sizeVector.x * indexVector.y) + (sizeVector.x * indexVector.z) + indexVector.x; // XZY ordering
|
||||
|
||||
const mappingIndex = blockMapping.get(block.blockInfo.name);
|
||||
ASSERT(mappingIndex !== undefined, 'Invalid mapping index');
|
||||
|
||||
buffer[bufferIndex] = mappingIndex;
|
||||
});
|
||||
|
||||
return buffer;
|
||||
@ -86,9 +99,9 @@ export class Litematic extends IExporter {
|
||||
private _encodeBlockBuffer(blockMesh: BlockMesh, blockMapping: BlockMapping) {
|
||||
const blockBuffer = this._createBlockBuffer(blockMesh, blockMapping);
|
||||
|
||||
const paletteSize = Object.keys(blockMapping).length;
|
||||
const paletteSize = blockMapping.size;
|
||||
const stride = Math.ceil(Math.log2(paletteSize - 1));
|
||||
ASSERT(stride >= 1, 'Stride too small');
|
||||
ASSERT(stride >= 1, `Stride too small: ${stride}`);
|
||||
|
||||
const expectedLengthBits = blockBuffer.length * stride;
|
||||
const requiredLengthBits = ceilToNearest(expectedLengthBits, 64);
|
||||
@ -133,18 +146,20 @@ export class Litematic extends IExporter {
|
||||
}
|
||||
|
||||
private _createBlockStatePalette(blockMapping: BlockMapping) {
|
||||
const blockStatePalette = Array(Object.keys(blockMapping).length);
|
||||
for (const blockName of Object.keys(blockMapping)) {
|
||||
const index = blockMapping[blockName];
|
||||
blockStatePalette[index] = { Name: { type: TagType.String, value: blockName } };
|
||||
}
|
||||
blockStatePalette[0] = { Name: { type: TagType.String, value: 'minecraft:air' } };
|
||||
const blockStatePalette = Array(blockMapping.size);
|
||||
|
||||
blockMapping.forEach((mappingIndex, blockName) => {
|
||||
blockStatePalette[mappingIndex] = { Name: { type: TagType.String, value: blockName } };
|
||||
});
|
||||
|
||||
return blockStatePalette;
|
||||
}
|
||||
|
||||
private _convertToNBT(blockMesh: BlockMesh) {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
const bounds = blockMesh.getVoxelMesh()?.getBounds();
|
||||
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
|
||||
|
||||
const bufferSize = sizeVector.x * sizeVector.y * sizeVector.z;
|
||||
const blockMapping = this._createBlockMapping(blockMesh);
|
||||
|
||||
const blockStates = this._createBlockStates(blockMesh, blockMapping);
|
||||
@ -161,9 +176,9 @@ export class Litematic extends IExporter {
|
||||
Description: { type: TagType.String, value: '' },
|
||||
Size: {
|
||||
type: TagType.Compound, value: {
|
||||
x: { type: TagType.Int, value: this._sizeVector.x },
|
||||
y: { type: TagType.Int, value: this._sizeVector.y },
|
||||
z: { type: TagType.Int, value: this._sizeVector.z },
|
||||
x: { type: TagType.Int, value: sizeVector.x },
|
||||
y: { type: TagType.Int, value: sizeVector.y },
|
||||
z: { type: TagType.Int, value: sizeVector.z },
|
||||
},
|
||||
},
|
||||
Name: { type: TagType.String, value: '' },
|
||||
@ -190,9 +205,9 @@ export class Litematic extends IExporter {
|
||||
BlockStatePalette: { type: TagType.List, value: { type: TagType.Compound, value: blockStatePalette } },
|
||||
Size: {
|
||||
type: TagType.Compound, value: {
|
||||
x: { type: TagType.Int, value: this._sizeVector.x },
|
||||
y: { type: TagType.Int, value: this._sizeVector.y },
|
||||
z: { type: TagType.Int, value: this._sizeVector.z },
|
||||
x: { type: TagType.Int, value: sizeVector.x },
|
||||
y: { type: TagType.Int, value: sizeVector.y },
|
||||
z: { type: TagType.Int, value: sizeVector.z },
|
||||
},
|
||||
},
|
||||
PendingFluidTicks: { type: TagType.List, value: { type: TagType.Int, value: [] } },
|
||||
@ -209,29 +224,4 @@ export class Litematic extends IExporter {
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['litematic'],
|
||||
};
|
||||
}
|
||||
|
||||
getFormatName() {
|
||||
return 'Litematic';
|
||||
}
|
||||
|
||||
getFileExtension(): string {
|
||||
return 'litematic';
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string): boolean {
|
||||
const bounds = blockMesh.getVoxelMesh()?.getBounds();
|
||||
this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
|
||||
|
||||
const nbt = this._convertToNBT(blockMesh);
|
||||
saveNBT(nbt, filePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4,27 +4,19 @@ import { BlockMesh } from '../block_mesh';
|
||||
import { AppConstants } from '../constants';
|
||||
import { StatusHandler } from '../status';
|
||||
import { AppUtil } from '../util';
|
||||
import { saveNBT } from '../util/nbt_util';
|
||||
import { NBTUtil } from '../util/nbt_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IExporter } from './base_exporter';
|
||||
|
||||
export class NBTExporter extends IExporter {
|
||||
public override getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['nbt'],
|
||||
name: 'Structure Blocks',
|
||||
extension: 'nbt',
|
||||
};
|
||||
}
|
||||
|
||||
public override getFormatName() {
|
||||
return 'Structure Blocks';
|
||||
}
|
||||
|
||||
public override getFileExtension(): string {
|
||||
return 'nbt';
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string): boolean {
|
||||
public override export(blockMesh: BlockMesh, filePath: string) {
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
const sizeVector = bounds.getDimensions().add(1);
|
||||
|
||||
@ -100,12 +92,6 @@ export class NBTExporter extends IExporter {
|
||||
},
|
||||
};
|
||||
|
||||
saveNBT(nbt, filePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static _getBufferIndex(dimensions: Vector3, vec: Vector3) {
|
||||
return vec.x + (vec.z * dimensions.x) + (vec.y * dimensions.x * dimensions.z);
|
||||
NBTUtil.save(nbt, filePath);
|
||||
}
|
||||
}
|
||||
|
@ -7,21 +7,12 @@ import { ASSERT } from '../util/error_util';
|
||||
import { IExporter } from './base_exporter';
|
||||
|
||||
export class ObjExporter extends IExporter {
|
||||
public override getFormatFilter(): Electron.FileFilter {
|
||||
public override getFormatFilter() {
|
||||
return {
|
||||
name: 'Wavefront Obj',
|
||||
extensions: ['obj'],
|
||||
extension: 'obj',
|
||||
};
|
||||
}
|
||||
|
||||
public override getFileExtension(): string {
|
||||
return 'obj';
|
||||
}
|
||||
|
||||
public override getFormatName(): string {
|
||||
return 'Wavefront OBJ';
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filepath: string) {
|
||||
ASSERT(path.isAbsolute(filepath));
|
||||
const parsedPath = path.parse(filepath);
|
||||
@ -32,8 +23,6 @@ export class ObjExporter extends IExporter {
|
||||
|
||||
this._exportOBJ(filepathOBJ, blockMesh, parsedPath.name + '.mtl');
|
||||
this._exportMTL(filepathMTL, filepathTexture, blockMesh);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _exportOBJ(filepath: string, blockMesh: BlockMesh, mtlRelativePath: string) {
|
||||
@ -85,16 +74,16 @@ export class ObjExporter extends IExporter {
|
||||
buffers.forEach(({ buffer }) => {
|
||||
positionData.set(buffer.position.data, positionIndex);
|
||||
positionIndex += buffer.position.data.length;
|
||||
|
||||
|
||||
normalData.set(buffer.normal.data, normalIndex);
|
||||
normalIndex += buffer.normal.data.length;
|
||||
|
||||
|
||||
texcoordData.set(buffer.texcoord.data, texcoordIndex);
|
||||
texcoordIndex += buffer.texcoord.data.length;
|
||||
|
||||
|
||||
blockTexcoordData.set(buffer.blockTexcoord.data, blockTexcoordIndex);
|
||||
blockTexcoordIndex += buffer.blockTexcoord.data.length;
|
||||
|
||||
|
||||
indexData.set(buffer.indices.data, indicesIndex);
|
||||
indicesIndex += buffer.indices.data.length;
|
||||
});
|
||||
|
@ -5,29 +5,21 @@ import { AppConstants } from '../constants';
|
||||
import { AppUtil } from '../util';
|
||||
import { LOG } from '../util/log_util';
|
||||
import { MathUtil } from '../util/math_util';
|
||||
import { saveNBT } from '../util/nbt_util';
|
||||
import { NBTUtil } from '../util/nbt_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IExporter } from './base_exporter';
|
||||
|
||||
export class SchemExporter extends IExporter {
|
||||
private static SCHEMA_VERSION = 2;
|
||||
|
||||
public override getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['schem'],
|
||||
name: 'Sponge Schematic',
|
||||
extension: 'schem',
|
||||
};
|
||||
}
|
||||
|
||||
public override getFormatName() {
|
||||
return 'Sponge Schematic';
|
||||
}
|
||||
|
||||
public override getFileExtension(): string {
|
||||
return 'schem';
|
||||
}
|
||||
|
||||
private static SCHEMA_VERSION = 2;
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string): boolean {
|
||||
public override export(blockMesh: BlockMesh, filePath: string) {
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
const sizeVector = bounds.getDimensions().add(1);
|
||||
|
||||
@ -40,7 +32,7 @@ export class SchemExporter extends IExporter {
|
||||
let blockIndex = 1;
|
||||
for (const blockName of blockMesh.getBlockPalette()) {
|
||||
const namespacedBlockName = AppUtil.Text.namespaceBlock(blockName);
|
||||
|
||||
|
||||
blockMapping[namespacedBlockName] = { type: TagType.Int, value: blockIndex };
|
||||
++blockIndex;
|
||||
}
|
||||
@ -54,11 +46,11 @@ export class SchemExporter extends IExporter {
|
||||
const namespacedBlockName = AppUtil.Text.namespaceBlock(block.blockInfo.name);
|
||||
blockData[bufferIndex] = blockMapping[namespacedBlockName].value;
|
||||
}
|
||||
|
||||
|
||||
const blockEncoding: number[] = [];
|
||||
for (let i = 0; i < blockData.length; ++i) {
|
||||
let id = blockData[i];
|
||||
|
||||
|
||||
while ((id & -128) != 0) {
|
||||
blockEncoding.push(id & 127 | 128);
|
||||
id >>>= 7;
|
||||
@ -69,7 +61,7 @@ export class SchemExporter extends IExporter {
|
||||
for (let i = 0; i < blockEncoding.length; ++i) {
|
||||
blockEncoding[i] = MathUtil.int8(blockEncoding[i]);
|
||||
}
|
||||
|
||||
|
||||
const nbt: NBT = {
|
||||
type: TagType.Compound,
|
||||
name: 'Schematic',
|
||||
@ -85,7 +77,7 @@ export class SchemExporter extends IExporter {
|
||||
},
|
||||
};
|
||||
|
||||
saveNBT(nbt, filePath);
|
||||
NBTUtil.save(nbt, filePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -4,18 +4,31 @@ import { NBT, TagType } from 'prismarine-nbt';
|
||||
import { BlockMesh } from '../block_mesh';
|
||||
import { StatusHandler, StatusID } from '../status';
|
||||
import { LOG_WARN } from '../util/log_util';
|
||||
import { saveNBT } from '../util/nbt_util';
|
||||
import { NBTUtil } from '../util/nbt_util';
|
||||
import { AppPaths, PathUtil } from '../util/path_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IExporter } from './base_exporter';
|
||||
|
||||
export class Schematic extends IExporter {
|
||||
private _convertToNBT(blockMesh: BlockMesh) {
|
||||
const bufferSize = this._sizeVector.x * this._sizeVector.y * this._sizeVector.z;
|
||||
public override getFormatFilter() {
|
||||
return {
|
||||
name: 'Schematic',
|
||||
extension: 'schematic',
|
||||
};
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string) {
|
||||
const nbt = this._convertToNBT(blockMesh);
|
||||
NBTUtil.save(nbt, filePath);
|
||||
}
|
||||
|
||||
private _convertToNBT(blockMesh: BlockMesh): NBT {
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
const sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
|
||||
|
||||
const bufferSize = sizeVector.x * sizeVector.y * sizeVector.z;
|
||||
const blocksData = Array<number>(bufferSize);
|
||||
const metaData = Array<number>(bufferSize);
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
|
||||
const schematicBlocks: { [blockName: string]: { id: number, meta: number, name: string } } = JSON.parse(
|
||||
fs.readFileSync(PathUtil.join(AppPaths.Get.resources, './block_ids.json'), 'utf8'),
|
||||
@ -26,7 +39,7 @@ export class Schematic extends IExporter {
|
||||
let numBlocksUnsupported = 0;
|
||||
for (const block of blocks) {
|
||||
const indexVector = Vector3.sub(block.voxel.position, bounds.min);
|
||||
const index = this._getBufferIndex(indexVector, this._sizeVector);
|
||||
const index = this._getBufferIndex(indexVector, sizeVector);
|
||||
if (block.blockInfo.name in schematicBlocks) {
|
||||
const schematicBlock = schematicBlocks[block.blockInfo.name];
|
||||
blocksData[index] = new Int8Array([schematicBlock.id])[0];
|
||||
@ -52,9 +65,9 @@ export class Schematic extends IExporter {
|
||||
type: TagType.Compound,
|
||||
name: 'Schematic',
|
||||
value: {
|
||||
Width: { type: TagType.Short, value: this._sizeVector.x },
|
||||
Height: { type: TagType.Short, value: this._sizeVector.y },
|
||||
Length: { type: TagType.Short, value: this._sizeVector.z },
|
||||
Width: { type: TagType.Short, value: sizeVector.x },
|
||||
Height: { type: TagType.Short, value: sizeVector.y },
|
||||
Length: { type: TagType.Short, value: sizeVector.z },
|
||||
Materials: { type: TagType.String, value: 'Alpha' },
|
||||
Blocks: { type: TagType.ByteArray, value: blocksData },
|
||||
Data: { type: TagType.ByteArray, value: metaData },
|
||||
@ -66,32 +79,7 @@ export class Schematic extends IExporter {
|
||||
return nbt;
|
||||
}
|
||||
|
||||
_getBufferIndex(vec: Vector3, sizeVector: Vector3) {
|
||||
private _getBufferIndex(vec: Vector3, sizeVector: Vector3) {
|
||||
return (sizeVector.z * sizeVector.x * vec.y) + (sizeVector.x * vec.z) + vec.x;
|
||||
}
|
||||
|
||||
getFormatFilter() {
|
||||
return {
|
||||
name: this.getFormatName(),
|
||||
extensions: ['schematic'],
|
||||
};
|
||||
}
|
||||
|
||||
getFormatName() {
|
||||
return 'Schematic';
|
||||
}
|
||||
|
||||
getFileExtension(): string {
|
||||
return 'schematic';
|
||||
}
|
||||
|
||||
public override export(blockMesh: BlockMesh, filePath: string): boolean {
|
||||
const bounds = blockMesh.getVoxelMesh().getBounds();
|
||||
this._sizeVector = Vector3.sub(bounds.max, bounds.min).add(1);
|
||||
|
||||
const nbt = this._convertToNBT(blockMesh);
|
||||
saveNBT(nbt, filePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,13 @@ import zlib from 'zlib';
|
||||
|
||||
import { ASSERT } from './error_util';
|
||||
|
||||
export function saveNBT(nbt: NBT, filepath: string) {
|
||||
ASSERT(path.isAbsolute(filepath), '[saveNBT]: filepath is not absolute');
|
||||
export namespace NBTUtil {
|
||||
export function save(nbt: NBT, filepath: string) {
|
||||
ASSERT(path.isAbsolute(filepath), '[saveNBT]: filepath is not absolute');
|
||||
|
||||
const uncompressedBuffer = writeUncompressed(nbt, 'big');
|
||||
const compressedBuffer = zlib.gzipSync(uncompressedBuffer);
|
||||
fs.writeFileSync(filepath, compressedBuffer);
|
||||
}
|
||||
|
||||
const uncompressedBuffer = writeUncompressed(nbt, 'big');
|
||||
const compressedBuffer = zlib.gzipSync(uncompressedBuffer);
|
||||
fs.writeFileSync(filepath, compressedBuffer);
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ export class WorkerClient {
|
||||
ASSERT(this._loadedBlockMesh !== undefined);
|
||||
|
||||
const exporter: IExporter = ExporterFactory.GetExporter(params.exporter);
|
||||
const fileExtension = '.' + exporter.getFileExtension();
|
||||
const fileExtension = '.' + exporter.getFormatFilter().extension;
|
||||
if (!params.filepath.endsWith(fileExtension)) {
|
||||
params.filepath += fileExtension;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
test('FULL Obj->Obj', () => {
|
||||
test('FULL Obj->Litematic', () => {
|
||||
TEST_PREAMBLE();
|
||||
|
||||
const config: THeadlessConfig = baseConfig;
|
||||
|
@ -40,7 +40,7 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
test('FULL Obj->Obj', () => {
|
||||
test('FULL Obj->Schem', () => {
|
||||
TEST_PREAMBLE();
|
||||
|
||||
const config: THeadlessConfig = baseConfig;
|
||||
|
Loading…
Reference in New Issue
Block a user